Skip to content

tenets.core.analysis.implementations

tenets.core.analysis.implementations

Language-specific code analyzers.

This package contains implementations of language analyzers for various programming languages. Each analyzer provides language-specific parsing and analysis capabilities.

Available analyzers: - PythonAnalyzer: Python code analysis with AST parsing - JavaScriptAnalyzer: JavaScript/TypeScript analysis - JavaAnalyzer: Java code analysis - GoAnalyzer: Go language analysis - RustAnalyzer: Rust code analysis - CppAnalyzer: C/C++ code analysis - CSharpAnalyzer: C# code analysis - SwiftAnalyzer: Swift code analysis - RubyAnalyzer: Ruby code analysis - PhpAnalyzer: PHP code analysis - KotlinAnalyzer: Kotlin code analysis - ScalaAnalyzer: Scala code analysis - DartAnalyzer: Dart code analysis - GDScriptAnalyzer: GDScript (Godot) analysis - HTMLAnalyzer: HTML markup analysis - CSSAnalyzer: CSS stylesheet analysis - GenericAnalyzer: Fallback for unsupported languages

CppAnalyzer

Python
CppAnalyzer()

Bases: LanguageAnalyzer

C/C++ code analyzer.

Provides analysis for C and C++ files including: - Include directive analysis (system and local) - Class, struct, and union extraction - Template analysis - Function and method extraction - Namespace handling - Macro and preprocessor directive analysis - Modern C++ features (auto, lambdas, smart pointers) - STL usage detection - Memory management patterns

Supports both C and C++ with appropriate feature detection.

Initialize the C++ analyzer with logger.

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def __init__(self):
    """Initialize the C++ analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract includes from C/C++ code.

Handles: - System includes: #include - Local includes: #include "myheader.h" - Conditional includes with #ifdef - Include guards

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects representing includes

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract includes from C/C++ code.

    Handles:
    - System includes: #include <iostream>
    - Local includes: #include "myheader.h"
    - Conditional includes with #ifdef
    - Include guards

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects representing includes
    """
    imports = []
    lines = content.split("\n")

    # Track preprocessor state
    ifdef_stack = []
    current_condition = True

    for i, line in enumerate(lines, 1):
        stripped = line.strip()

        # Handle preprocessor conditionals
        if stripped.startswith("#ifdef") or stripped.startswith("#ifndef"):
            condition = stripped.split()[1] if len(stripped.split()) > 1 else ""
            ifdef_stack.append(current_condition)
            # We'll track all includes regardless of conditionals for analysis
            continue
        elif stripped.startswith("#if"):
            ifdef_stack.append(current_condition)
            continue
        elif stripped.startswith("#else"):
            if ifdef_stack:
                current_condition = not current_condition
            continue
        elif stripped.startswith("#elif"):
            continue
        elif stripped.startswith("#endif"):
            if ifdef_stack:
                current_condition = ifdef_stack.pop()
            continue

        # System includes
        system_include = re.match(r"^\s*#\s*include\s*<([^>]+)>", line)
        if system_include:
            header = system_include.group(1)
            imports.append(
                ImportInfo(
                    module=header,
                    line=i,
                    type="system",
                    is_relative=False,
                    is_stdlib=self._is_stdlib_header(header),
                    is_stl=self._is_stl_header(header),
                    conditional=len(ifdef_stack) > 0,
                )
            )
            continue

        # Local includes
        local_include = re.match(r'^\s*#\s*include\s*"([^"]+)"', line)
        if local_include:
            header = local_include.group(1)
            imports.append(
                ImportInfo(
                    module=header,
                    line=i,
                    type="local",
                    is_relative=True,
                    is_project_header=True,
                    conditional=len(ifdef_stack) > 0,
                )
            )
            continue

    # Detect include guards
    self._detect_include_guards(content, imports)

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from C/C++ code.

In C/C++, symbols are exported by default unless static. For headers, we extract declarations. For source files, we extract non-static definitions.

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from C/C++ code.

    In C/C++, symbols are exported by default unless static.
    For headers, we extract declarations. For source files,
    we extract non-static definitions.

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols
    """
    exports = []
    is_header = file_path.suffix in [".h", ".hh", ".hpp", ".hxx", ".h++"]

    # Extract namespace if present
    namespace = self._extract_namespace(content)

    # Non-static functions
    func_pattern = r"^(?:template\s*<[^>]*>\s*)?(?!static)(?:(?:inline|extern|virtual|explicit|constexpr)\s+)*(?:[\w\s\*&:<>]+)\s+(\w+)\s*\([^)]*\)(?:\s*const)?(?:\s*noexcept)?(?:\s*override)?(?:\s*final)?(?:\s*=\s*0)?(?:\s*(?:\{|;))"

    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        # Filter out keywords
        if func_name not in [
            "if",
            "for",
            "while",
            "switch",
            "return",
            "delete",
            "new",
            "throw",
            "catch",
        ]:
            line_content = content[match.start() : match.end()]
            before_window = content[max(0, match.start() - 200) : match.start()]
            is_tmpl = (
                ("template" in line_content)
                or ("template" in before_window)
                or self._is_template_function(content, match.start())
            )
            exports.append(
                {
                    "name": func_name,
                    "type": "function",
                    "line": content[: match.start()].count("\n") + 1,
                    "namespace": namespace,
                    "is_inline": "inline" in line_content,
                    "is_virtual": "virtual" in line_content,
                    "is_pure_virtual": "= 0" in line_content,
                    "is_constexpr": "constexpr" in line_content,
                    "is_template": is_tmpl,
                }
            )

    # Classes and structs (public by default in struct)
    class_pattern = r"\b(?:struct|(?<!enum\s)class)\s+(?:__declspec\([^)]+\)\s+)?(\w+)(?:\s*:\s*(?:public|private|protected)\s+[\w:]+)?(?:\s*\{|;)"
    for match in re.finditer(class_pattern, content):
        class_name = match.group(1)
        is_struct = "struct" in match.group(0)
        # Find keyword position for accurate template check
        inner = match.group(0)
        kw = "struct" if "struct" in inner else "class"
        kw_pos = match.start() + inner.find(kw)

        exports.append(
            {
                "name": class_name,
                "type": "struct" if is_struct else "class",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "default_visibility": "public" if is_struct else "private",
                "is_template": self._is_template_class(content, kw_pos),
            }
        )

    # Enums
    enum_pattern = r"\benum\s+(?:class\s+)?(\w+)(?:\s*:\s*\w+)?(?:\s*\{|;)"

    for match in re.finditer(enum_pattern, content):
        enum_name = match.group(1)
        is_enum_class = "enum class" in match.group(0)

        exports.append(
            {
                "name": enum_name,
                "type": "enum_class" if is_enum_class else "enum",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Unions
    union_pattern = r"\bunion\s+(\w+)(?:\s*\{|;)"

    for match in re.finditer(union_pattern, content):
        exports.append(
            {
                "name": match.group(1),
                "type": "union",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Typedefs and using declarations
    typedef_pattern = r"\btypedef\s+.*?\s+(\w+)\s*;"

    for match in re.finditer(typedef_pattern, content):
        exports.append(
            {
                "name": match.group(1),
                "type": "typedef",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    using_pattern = r"\busing\s+(\w+)\s*="

    for match in re.finditer(using_pattern, content):
        exports.append(
            {
                "name": match.group(1),
                "type": "using_alias",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Global variables (non-static)
    if not is_header:
        var_pattern = (
            r"^(?!static)(?:extern\s+)?(?:const\s+)?(?:[\w\s\*&:<>]+)\s+(\w+)\s*(?:=|;)"
        )

        for match in re.finditer(var_pattern, content, re.MULTILINE):
            var_name = match.group(1)
            if var_name not in [
                "if",
                "for",
                "while",
                "return",
                "class",
                "struct",
                "enum",
                "typedef",
                "using",
            ]:
                exports.append(
                    {
                        "name": var_name,
                        "type": "variable",
                        "line": content[: match.start()].count("\n") + 1,
                        "namespace": namespace,
                        "is_const": "const" in match.group(0),
                        "is_extern": "extern" in match.group(0),
                    }
                )

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from C/C++ file.

Extracts: - Namespaces - Classes and structs with inheritance - Functions and methods - Templates - Macros and preprocessor directives - Global variables - Operator overloads

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from C/C++ file.

    Extracts:
    - Namespaces
    - Classes and structs with inheritance
    - Functions and methods
    - Templates
    - Macros and preprocessor directives
    - Global variables
    - Operator overloads

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Determine if it's C or C++
    is_cpp = self._is_cpp_file(file_path, content)
    structure.language_variant = "C++" if is_cpp else "C"

    # Extract namespaces (C++ only)
    if is_cpp:
        namespace_pattern = r"namespace\s+(\w+)\s*\{"
        for match in re.finditer(namespace_pattern, content):
            structure.namespaces.append(
                {"name": match.group(1), "line": content[: match.start()].count("\n") + 1}
            )

    # Extract classes and structs
    class_pattern = r"(?:template\s*<[^>]+>\s*)?(?:struct|(?<!enum\s)class)\s+(\w+)(?:\s*:\s*((?:public|private|protected)\s+[\w:]+(?:\s*,\s*(?:public|private|protected)\s+[\w:]+)*))?"

    for match in re.finditer(class_pattern, content):
        class_name = match.group(1)
        inheritance = match.group(2)

        # Parse inheritance
        bases = []
        if inheritance:
            for base in inheritance.split(","):
                base = base.strip()
                # Remove access specifier
                base = re.sub(r"^(public|private|protected)\s+", "", base)
                bases.append(base)

        # Find class body
        class_start = match.end()
        class_body = self._extract_class_body(content, class_start)

        # Extract methods and members
        methods = []
        fields = []

        if class_body:
            methods = self._extract_class_methods(class_body)
            fields = self._extract_class_fields(class_body)

        inner = match.group(0)
        kw = "struct" if "struct" in inner else "class"
        kw_pos = match.start() + inner.find(kw)
        class_info = ClassInfo(
            name=class_name,
            line=content[: match.start()].count("\n") + 1,
            bases=bases,
            methods=methods,
            fields=fields,
            is_struct="struct" in match.group(0),
            is_template=self._is_template_class(content, kw_pos),
        )

        structure.classes.append(class_info)

    # Extract standalone functions
    func_pattern = r"(?:template\s*<[^>]+>\s*)?(?:(?:inline|static|extern|virtual|explicit|constexpr)\s+)*(?:[\w\s\*&:<>]+)\s+(\w+)\s*\([^)]*\)(?:\s*const)?(?:\s*noexcept)?(?:\s*\{|;)"

    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)

        # Filter out keywords and methods
        if func_name in [
            "if",
            "for",
            "while",
            "switch",
            "return",
            "delete",
            "new",
            "throw",
            "catch",
        ]:
            continue

        # Check if it's inside a class (simple heuristic)
        if self._is_inside_class(content, match.start()):
            continue

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            is_static="static" in match.group(0),
            is_inline="inline" in match.group(0),
            is_constexpr="constexpr" in match.group(0),
            is_template="template" in content[max(0, match.start() - 100) : match.start()],
            is_exported="static" not in match.group(0),
        )

        structure.functions.append(func_info)

    # Extract templates
    template_pattern = r"template\s*<([^>]+)>\s*(?:class|struct|typename|function)\s+(\w+)"

    for match in re.finditer(template_pattern, content):
        structure.templates.append(
            {
                "name": match.group(2),
                "parameters": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract macros
    macro_pattern = r"^\s*#define\s+(\w+)(?:\([^)]*\))?"

    for match in re.finditer(macro_pattern, content, re.MULTILINE):
        macro_name = match.group(1)
        is_function_macro = "(" in match.group(0)

        structure.macros.append(
            {
                "name": macro_name,
                "line": content[: match.start()].count("\n") + 1,
                "is_function_macro": is_function_macro,
            }
        )

    # Extract global variables
    global_var_pattern = (
        r"^(?:static\s+)?(?:const\s+)?(?:[\w\s\*&:<>]+)\s+(\w+)\s*(?:=\s*[^;]+)?\s*;"
    )

    for match in re.finditer(global_var_pattern, content, re.MULTILINE):
        var_name = match.group(1)

        # Filter out function declarations and keywords
        if var_name in ["if", "for", "while", "return", "class", "struct", "enum", "typedef"]:
            continue

        if not self._is_inside_class(content, match.start()) and not self._is_inside_function(
            content, match.start()
        ):
            structure.variables.append(
                {
                    "name": var_name,
                    "line": content[: match.start()].count("\n") + 1,
                    "type": "global",
                    "is_static": "static" in match.group(0),
                    "is_const": "const" in match.group(0),
                }
            )

    # Extract unions
    union_pattern = r"union\s+(\w+)\s*\{"

    for match in re.finditer(union_pattern, content):
        structure.unions.append(
            {"name": match.group(1), "line": content[: match.start()].count("\n") + 1}
        )

    # Extract operator overloads
    operator_pattern = r"operator\s*(?:[\+\-\*\/\%\^\&\|\~\!\=\<\>\[\]\(\)]|\+\+|\-\-|\<\<|\>\>|\=\=|\!\=|\<\=|\>\=|\&\&|\|\||\+\=|\-\=|\*\=|\/\=|\%\=|\^\=|\&\=|\|\=|\<\<\=|\>\>\=|,|->\*?|new|delete)(?:\s*\[\])?"

    operator_count = len(re.findall(operator_pattern, content))
    structure.operator_overloads = operator_count

    # Detect STL usage (boolean for test compatibility)
    stl_types_found = self._detect_stl_usage(content)
    structure.uses_stl = bool(stl_types_found)
    structure.stl_types = stl_types_found  # Optionally keep the list for other uses

    # Detect smart pointers
    structure.smart_pointers = self._detect_smart_pointers(content)

    # Count lambda expressions
    lambda_pattern = r"\[[^\]]*\]\s*\([^)]*\)\s*(?:->[\w\s]+)?\s*\{"
    structure.lambda_count = len(re.findall(lambda_pattern, content))

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for C/C++ code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Preprocessor complexity - Template complexity - Memory management complexity

PARAMETERDESCRIPTION
content

C/C++ source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/cpp_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for C/C++ code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Preprocessor complexity
    - Template complexity
    - Memory management complexity

    Args:
        content: C/C++ source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bcatch\b",
        r"\b&&\b",
        r"\|\|",
        r"\?",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments and preprocessor directives
        if (
            line.strip().startswith("//")
            or line.strip().startswith("/*")
            or line.strip().startswith("#")
        ):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\bfor\b", 1),
            (r"\bwhile\b", 1),
            (r"\bswitch\b", 1),
            (r"\btry\b", 1),
            (r"\bcatch\b", 1),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = self._count_code_lines(content)
    metrics.comment_lines = self._count_comment_lines(content)
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count functions
    metrics.function_count = len(re.findall(r"[\w\s\*&:<>]+\s+\w+\s*\([^)]*\)\s*\{", content))

    # Count classes and structs
    metrics.class_count = len(re.findall(r"\b(?:class|struct)\s+\w+", content))

    # Template metrics
    metrics.template_count = len(re.findall(r"template\s*<", content))
    metrics.template_specializations = len(re.findall(r"template\s*<>", content))

    # Preprocessor metrics
    metrics.macro_count = len(re.findall(r"^\s*#define\s+", content, re.MULTILINE))
    metrics.ifdef_count = len(re.findall(r"^\s*#if(?:def|ndef)?\s+", content, re.MULTILINE))
    metrics.include_count = len(re.findall(r"^\s*#include\s+", content, re.MULTILINE))

    # Memory management metrics
    metrics.new_count = len(re.findall(r"\bnew\s+", content))
    # Count delete and delete[]
    metrics.delete_count = len(re.findall(r"\bdelete\s*(?:\[\])?", content))
    metrics.malloc_count = len(re.findall(r"\bmalloc\s*\(", content))
    metrics.free_count = len(re.findall(r"\bfree\s*\(", content))

    # Smart pointer usage (count both types and factory helpers)
    metrics.unique_ptr_count = len(re.findall(r"\bunique_ptr\s*<", content)) + len(
        re.findall(r"(?:\b[\w:]+::)?make_unique(?:\s*<[^>]+>)?\s*\(", content)
    )
    metrics.shared_ptr_count = len(re.findall(r"\bshared_ptr\s*<", content)) + len(
        re.findall(r"(?:\b[\w:]+::)?make_shared(?:\s*<[^>]+>)?\s*\(", content)
    )
    metrics.weak_ptr_count = len(re.findall(r"\bweak_ptr\s*<", content))

    # RAII indicators
    metrics.uses_raii = (
        metrics.unique_ptr_count > 0 or metrics.shared_ptr_count > 0 or "RAII" in content
    )

    # Calculate memory safety score
    manual_memory = (
        metrics.new_count + metrics.delete_count + metrics.malloc_count + metrics.free_count
    )
    smart_memory = metrics.unique_ptr_count + metrics.shared_ptr_count

    if manual_memory + smart_memory > 0:
        metrics.memory_safety_score = smart_memory / (manual_memory + smart_memory)
    else:
        metrics.memory_safety_score = 1.0

    # Calculate maintainability index
    if metrics.code_lines > 0:
        # Adjusted for C++ complexity
        template_factor = 1 - (metrics.template_count * 0.02)
        memory_factor = metrics.memory_safety_score

        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 10 * template_factor
            + 15 * memory_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

CSharpAnalyzer

Python
CSharpAnalyzer()

Bases: LanguageAnalyzer

C# code analyzer with Unity3D support.

Provides comprehensive analysis for C# files including: - Using directives and namespace analysis - Class, interface, struct, enum, and record extraction - Property and event analysis - Async/await and Task-based patterns - LINQ query detection - Attribute processing - Unity3D specific patterns (MonoBehaviour, Coroutines, etc.) - .NET Framework/Core detection - Nullable reference types (C# 8+) - Pattern matching (C# 7+)

Supports modern C# features and Unity3D development patterns.

Initialize the C# analyzer with logger.

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def __init__(self):
    """Initialize the C# analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract using directives from C# code.

Handles: - using statements: using System.Collections.Generic; - using static: using static System.Math; - using aliases: using Project = PC.MyCompany.Project; - global using (C# 10+): global using System.Text; - Unity-specific usings

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract using directives from C# code.

    Handles:
    - using statements: using System.Collections.Generic;
    - using static: using static System.Math;
    - using aliases: using Project = PC.MyCompany.Project;
    - global using (C# 10+): global using System.Text;
    - Unity-specific usings

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports: List[ImportInfo] = []
    lines = content.splitlines()

    current_namespace: Optional[str] = None
    seen_code = False  # stop parsing usings after first non-using code element at top-level

    # Pre-compile patterns (hot path in large files)
    namespace_re = re.compile(r"^\s*namespace\s+([\w\.]+)")
    alias_re = re.compile(r"^\s*(?:(global)\s+)?using\s+([\w\.]+)\s*=\s*([^;]+?)\s*;")
    using_re = re.compile(r"^\s*(?:(global)\s+)?using\s+(?:(static)\s+)?([\w\.]+)\s*;")
    decl_re = re.compile(
        r"^\s*(?:public\s+)?(?:partial\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:class|interface|struct|enum|delegate|record)\b"
    )

    for i, line in enumerate(lines, 1):
        stripped = line.strip()
        if not stripped:
            continue
        # Skip single-line comments
        if stripped.startswith("//"):
            continue

        # Namespace (track for nested usings)
        m = namespace_re.match(line)
        if m:
            current_namespace = m.group(1)
            # Don't treat namespace declaration itself as code for stopping further usings
            continue

        # Stop scanning after first real code (class/interface/etc.) at top-level
        if decl_re.match(line):
            seen_code = True
        if seen_code:
            # Still allow usings inside namespace blocks (indented) – C# allows that
            # Only break if this is a top-level code declaration and not inside a namespace context yet
            if current_namespace is None:
                break

        # Using alias
        m = alias_re.match(line)
        if m:
            is_global = m.group(1) == "global"
            alias = m.group(2)
            target = m.group(3).strip()
            base_for_category = target.split("<", 1)[0].strip()
            category = self._categorize_import(base_for_category)
            is_unity = self._is_unity_import(base_for_category)
            imports.append(
                ImportInfo(
                    module=target,
                    alias=alias,
                    line=i,
                    type="global_using_alias" if is_global else "using_alias",
                    is_relative=False,
                    category=category,
                    is_unity=is_unity,
                    namespace_context=current_namespace,
                )
            )
            continue

        # Standard / static / global usings
        m = using_re.match(line)
        if m:
            is_global = m.group(1) == "global"
            is_static = m.group(2) == "static"
            ns = m.group(3)
            category = self._categorize_import(ns)
            is_unity = self._is_unity_import(ns)
            if is_global:
                import_type = "global_using"
            elif is_static:
                import_type = "using_static"
            else:
                import_type = "using"
            imports.append(
                ImportInfo(
                    module=ns,
                    line=i,
                    type=import_type,
                    is_relative=False,
                    category=category,
                    is_unity=is_unity,
                    namespace_context=current_namespace,
                )
            )
            continue

    # .csproj dependency parsing
    if file_path.suffix.lower() == ".csproj":
        imports.extend(self._extract_csproj_dependencies(content))

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract public members from C# code.

In C#, public members are accessible from other assemblies. This includes public classes, interfaces, structs, enums, delegates, etc.

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported (public) symbols

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract public members from C# code.

    In C#, public members are accessible from other assemblies.
    This includes public classes, interfaces, structs, enums, delegates, etc.

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported (public) symbols
    """
    exports = []

    # Extract namespace
    namespace_match = re.search(r"^\s*namespace\s+([\w\.]+)", content, re.MULTILINE)
    namespace = namespace_match.group(1) if namespace_match else ""

    # Public classes (including Unity MonoBehaviours)
    class_pattern = r"^\s*(?:public\s+)?(?:partial\s+)?(?:abstract\s+)?(?:sealed\s+)?(?:static\s+)?class\s+(\w+)(?:\s*:\s*([\w\.,\s]+))?"

    for match in re.finditer(class_pattern, content, re.MULTILINE):
        class_name = match.group(1)
        inheritance = match.group(2)

        modifiers = []
        if "abstract" in match.group(0):
            modifiers.append("abstract")
        if "sealed" in match.group(0):
            modifiers.append("sealed")
        if "static" in match.group(0):
            modifiers.append("static")
        if "partial" in match.group(0):
            modifiers.append("partial")

        # Check if it's a Unity component
        is_unity_component = False
        unity_base_class = None
        if inheritance:
            if "MonoBehaviour" in inheritance:
                is_unity_component = True
                unity_base_class = "MonoBehaviour"
            elif "ScriptableObject" in inheritance:
                is_unity_component = True
                unity_base_class = "ScriptableObject"
            elif "Editor" in inheritance:
                is_unity_component = True
                unity_base_class = "Editor"

        exports.append(
            {
                "name": class_name,
                "type": "class",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "modifiers": modifiers,
                "inheritance": inheritance,
                "is_unity_component": is_unity_component,
                "unity_base_class": unity_base_class,
            }
        )

    # Public interfaces
    interface_pattern = r"^\s*(?:public\s+)?(?:partial\s+)?interface\s+(\w+)(?:<[^>]+>)?(?:\s*:\s*([\w\.,\s]+))?"

    for match in re.finditer(interface_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "interface",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "extends": match.group(2),
            }
        )

    # Public structs
    struct_pattern = r"^\s*(?:public\s+)?(?:readonly\s+)?(?:ref\s+)?struct\s+(\w+)"

    for match in re.finditer(struct_pattern, content, re.MULTILINE):
        modifiers = []
        if "readonly" in match.group(0):
            modifiers.append("readonly")
        if "ref" in match.group(0):
            modifiers.append("ref")

        exports.append(
            {
                "name": match.group(1),
                "type": "struct",
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "modifiers": modifiers,
            }
        )

    # Public enums (support both 'enum' and 'enum class' styles)
    enum_pattern = r"^\s*(?:public\s+)?enum(?:\s+class)?\s+(\w+)(?:\s*:\s*([\w\.]+))?"

    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        enum_type = "enum_class" if "enum class" in match.group(0) else "enum"
        exports.append(
            {
                "name": match.group(1),
                "type": enum_type,
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
                "base_type": match.group(2),
            }
        )

    # Public delegates
    delegate_pattern = r"^\s*(?:public\s+)?delegate\s+(\w+)\s+(\w+(?:<[^>]+>)?)\s*\([^)]*\)"

    for match in re.finditer(delegate_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(2),
                "type": "delegate",
                "return_type": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    # Public records (C# 9+)
    record_pattern = r"^\s*(?:public\s+)?record\s+(?:class\s+|struct\s+)?(\w+)"

    for match in re.finditer(record_pattern, content, re.MULTILINE):
        record_type = "record_struct" if "struct" in match.group(0) else "record"
        exports.append(
            {
                "name": match.group(1),
                "type": record_type,
                "line": content[: match.start()].count("\n") + 1,
                "namespace": namespace,
            }
        )

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from C# file.

Extracts: - Namespace declarations - Classes with inheritance and interfaces - Properties with getters/setters - Methods including async methods - Events and delegates - Unity-specific components (MonoBehaviours, Coroutines) - LINQ queries - Attributes

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from C# file.

    Extracts:
    - Namespace declarations
    - Classes with inheritance and interfaces
    - Properties with getters/setters
    - Methods including async methods
    - Events and delegates
    - Unity-specific components (MonoBehaviours, Coroutines)
    - LINQ queries
    - Attributes

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Extract namespace
    namespace_match = re.search(r"^\s*namespace\s+([\w\.]+)", content, re.MULTILINE)
    if namespace_match:
        structure.namespace = namespace_match.group(1)

    # Detect if it's a Unity script
    structure.is_unity_script = self._is_unity_script(content)

    # Extract classes
    # Capture any stacked attribute blocks immediately preceding the class declaration in a named group
    # so we don't rely on a fragile backward scan that fails when the regex itself already consumed them.
    class_pattern = (
        r"(?:^|\n)\s*(?P<attr_block>(?:\[[^\]]+\]\s*)*)"
        r"(?:(?P<visibility>public|private|protected|internal)\s+)?"
        r"(?:(?P<partial>partial)\s+)?(?:(?P<abstract>abstract)\s+)?(?:(?P<sealed>sealed)\s+)?(?:(?P<static>static)\s+)?"
        r"class\s+(?P<class_name>\w+)(?:<(?P<generics>[^>]+)>)?(?:\s*:\s*(?P<inheritance>[\w\.,\s<>]+))?"
    )

    for match in re.finditer(class_pattern, content):
        attr_block = match.group("attr_block") or ""
        class_name = match.group("class_name") or ""
        generics = match.group("generics")
        inheritance = match.group("inheritance")

        # Prefer directly captured attribute block; fallback to legacy backward scan only if empty
        attributes = self._extract_attributes(attr_block) if attr_block else []
        if not attributes:
            # Legacy backward scan (kept for robustness in edge cases where regex miss might occur)
            start_line_index = content[: match.start()].count("\n")
            lines = content.splitlines()
            attr_lines: List[str] = []
            line_cursor = start_line_index - 1
            while line_cursor >= 0:
                line_text = lines[line_cursor].strip()
                if not line_text or not line_text.startswith("["):
                    break
                attr_lines.insert(0, line_text)
                line_cursor -= 1
            if attr_lines:
                attributes = self._extract_attributes("\n".join(attr_lines))

        # Collect modifiers
        modifiers: List[str] = []
        for key in ["partial", "abstract", "sealed", "static"]:
            if match.group(key):
                modifiers.append(match.group(key))

        visibility = match.group("visibility") or None

        # Parse inheritance
        bases = []
        interfaces = []
        is_monobehaviour = False
        is_scriptable_object = False

        if inheritance:
            for item in inheritance.split(","):
                item = item.strip()
                if item == "MonoBehaviour":
                    is_monobehaviour = True
                    bases.append(item)
                elif item == "ScriptableObject":
                    is_scriptable_object = True
                    bases.append(item)
                elif item.startswith("I"):  # Convention for interfaces
                    interfaces.append(item)
                else:
                    bases.append(item)

        # Find class body
        class_body = self._extract_class_body(content, match.end())

        # Extract class components
        methods = []
        properties = []
        fields = []
        events = []
        unity_methods = []
        coroutines = []

        if class_body:
            methods = self._extract_methods(class_body)
            properties = self._extract_properties(class_body)
            fields = self._extract_fields(class_body)
            events = self._extract_events(class_body)

            if is_monobehaviour or is_scriptable_object:
                unity_methods = self._extract_unity_methods(class_body)
                coroutines = self._extract_coroutines(class_body)

        class_info = ClassInfo(
            name=class_name,
            line=content[: match.start()].count("\n") + 1,
            generics=generics,
            bases=bases,
            interfaces=interfaces,
            visibility=visibility,
            modifiers=modifiers,
            methods=methods,
            properties=properties,
            fields=fields,
            events=events,
            attributes=attributes,
            is_monobehaviour=is_monobehaviour,
            is_scriptable_object=is_scriptable_object,
            unity_methods=unity_methods,
            coroutines=coroutines,
        )

        structure.classes.append(class_info)

    # Extract interfaces
    interface_pattern = r"(?:^|\n)\s*(?:public\s+)?(?:partial\s+)?interface\s+(\w+)(?:<([^>]+)>)?(?:\s*:\s*([\w\.,\s<>]+))?"

    for match in re.finditer(interface_pattern, content):
        interface_name = match.group(1)
        generics = match.group(2)
        extends = match.group(3)

        # Extract interface methods
        interface_body = self._extract_class_body(content, match.end())
        methods = self._extract_interface_methods(interface_body) if interface_body else []

        structure.interfaces.append(
            {
                "name": interface_name,
                "line": content[: match.start()].count("\n") + 1,
                "generics": generics,
                "extends": self._parse_interface_list(extends) if extends else [],
                "methods": methods,
            }
        )

    # Extract structs
    struct_pattern = (
        r"(?:^|\n)\s*(?:public\s+)?(?:readonly\s+)?(?:ref\s+)?struct\s+(\w+)(?:<([^>]+)>)?"
    )

    for match in re.finditer(struct_pattern, content):
        struct_name = match.group(1)
        generics = match.group(2)

        modifiers = []
        if "readonly" in match.group(0):
            modifiers.append("readonly")
        if "ref" in match.group(0):
            modifiers.append("ref")

        structure.structs.append(
            {
                "name": struct_name,
                "line": content[: match.start()].count("\n") + 1,
                "generics": generics,
                "modifiers": modifiers,
            }
        )

    # Extract enums
    enum_pattern = r"(?:^|\n)\s*(?:public\s+)?enum\s+(\w+)(?:\s*:\s*(\w+))?"

    for match in re.finditer(enum_pattern, content):
        enum_name = match.group(1)
        base_type = match.group(2)

        # Extract enum values
        enum_body = self._extract_class_body(content, match.end())
        values = self._extract_enum_values(enum_body) if enum_body else []

        structure.enums.append(
            {
                "name": enum_name,
                "line": content[: match.start()].count("\n") + 1,
                "base_type": base_type,
                "values": values,
            }
        )

    # Extract delegates
    delegate_pattern = r"(?:^|\n)\s*(?:public\s+)?delegate\s+(\w+)\s+(\w+)\s*\(([^)]*)\)"

    for match in re.finditer(delegate_pattern, content):
        structure.delegates.append(
            {
                "return_type": match.group(1),
                "name": match.group(2),
                "parameters": self._parse_parameters(match.group(3)),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract global functions (rare in C# but possible)
    structure.functions = self._extract_global_functions(content)

    # Extract LINQ queries
    structure.linq_queries = self._extract_linq_queries(content)

    # Count async methods
    structure.async_method_count = len(re.findall(r"\basync\s+(?:Task|ValueTask)", content))

    # Count lambda expressions
    structure.lambda_count = len(re.findall(r"=>\s*(?:\{|[^;{]+;)", content))

    # Detect framework
    structure.framework = self._detect_framework(content)

    # Check for test file
    structure.is_test_file = (
        "Test" in file_path.name
        or file_path.name.endswith("Tests.cs")
        or file_path.name.endswith("Test.cs")
        or any(part in ["Tests", "Test"] for part in file_path.parts)
    )

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for C# code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Unity-specific complexity (Coroutines, Update methods) - Async/await complexity - LINQ complexity - Exception handling complexity

PARAMETERDESCRIPTION
content

C# source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/csharp_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for C# code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Unity-specific complexity (Coroutines, Update methods)
    - Async/await complexity
    - LINQ complexity
    - Exception handling complexity

    Args:
        content: C# source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bforeach\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bcatch\b",
        r"\b&&\b",
        r"\|\|",
        r"\?\s*[^:]+\s*:",  # Ternary operator
        r"\?\?",  # Null coalescing operator
        r"\?\.(?!\s*\[)",  # Null conditional operator (not including ?.[])
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    # Add complexity for pattern matching (C# 7+)
    # "is" patterns
    complexity += len(re.findall(r"\bis\s+\w+\s+\w+", content))
    # Switch statements with when filters
    complexity += len(re.findall(r"\bswitch\s*\(.*\)\s*\{[\s\S]*?\bwhen\b", content))
    # Switch expressions with when clauses (=> and when)
    complexity += len(re.findall(r"\bswitch\s*\{[\s\S]*?=>[\s\S]*?\bwhen\b", content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.splitlines()
    for line in lines:
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\belse\s+if\b", 1),
            (r"\belse\b", 0),
            (r"\bfor\b", 1),
            (r"\bforeach\b", 1),
            (r"\bwhile\b", 1),
            (r"\bdo\b", 1),
            (r"\bswitch\b", 1),
            (r"\btry\b", 1),
            (r"\bcatch\b", 1),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

        metrics.cognitive = cognitive
        metrics.max_depth = max_nesting

        # Count code elements
        metrics.line_count = len(lines)
        metrics.code_lines = self._count_code_lines(content)
        metrics.comment_lines = self._count_comment_lines(content)
        metrics.comment_ratio = (
            metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
        )

        # Count classes, interfaces, etc.
        metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
        metrics.interface_count = len(re.findall(r"\binterface\s+\w+", content))
        metrics.struct_count = len(re.findall(r"\bstruct\s+\w+", content))
        metrics.enum_count = len(re.findall(r"\benum\s+\w+", content))

        # Count methods
        metrics.method_count = len(
            re.findall(
                r"(?:public|private|protected|internal)\s+(?:static\s+)?(?:async\s+)?(?:override\s+)?(?:virtual\s+)?(?:[\w<>\[\]]+)\s+\w+\s*\([^)]*\)\s*\{",
                content,
            )
        )

        # Property metrics
        metrics.property_count = len(
            re.findall(
                r"(?:public|private|protected|internal)\s+(?:static\s+)?(?:[\w<>\[\]]+)\s+\w+\s*\{\s*(?:get|set)",
                content,
            )
        )
        metrics.auto_property_count = len(re.findall(r"\{\s*get;\s*(?:set;)?\s*\}", content))

        # Exception handling metrics
        metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
        metrics.catch_blocks = len(
            re.findall(r"\bcatch(?:\s+when\s*\([^)]*\))?\s*(?:\([^)]*\))?\s*\{", content)
        )
        metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
        # Count both "throw;" and "throw new ..." forms
        metrics.throw_statements = len(re.findall(r"\bthrow\b", content))

        # Async/await metrics
        metrics.async_methods = len(re.findall(r"\basync\s+(?:Task|ValueTask)", content))
        metrics.await_statements = len(re.findall(r"\bawait\s+", content))

        # LINQ metrics
        metrics.linq_queries = len(re.findall(r"\bfrom\s+\w+\s+in\s+", content))
        metrics.linq_methods = len(
            re.findall(
                r"\.\s*(?:Where|Select|OrderBy|GroupBy|Join|Any|All|First|Last|Single)\s*\(",
                content,
            )
        )

        # Unity-specific metrics
        if self._is_unity_script(content):
            metrics.unity_components = len(
                re.findall(r":\s*(?:MonoBehaviour|ScriptableObject)", content)
            )
            metrics.coroutines = len(re.findall(r"\bIEnumerator\s+\w+\s*\(", content))
            metrics.unity_methods = len(
                re.findall(
                    r"\b(?:Start|Update|FixedUpdate|LateUpdate|OnEnable|OnDisable|Awake|OnDestroy|OnCollision(?:Enter|Exit|Stay)?|OnTrigger(?:Enter|Exit|Stay)?)\s*\(",
                    content,
                )
            )
            metrics.serialize_fields = len(re.findall(r"\[SerializeField\]", content))
            metrics.unity_events = len(re.findall(r"\bUnityEvent(?:<[^>]+>)?\s+\w+", content))

        # Attribute metrics
        metrics.attribute_count = len(re.findall(r"\[[A-Z]\w*(?:\([^)]*\))?\]", content))

        # Nullable reference types (C# 8+): properties and locals/params with ? type, plus #nullable enable
        nullable_types = len(re.findall(r"[\w<>\[\]]+\?\s+\w+\s*[;=,)\}]", content))
        metrics.nullable_refs = nullable_types + len(re.findall(r"#nullable\s+enable", content))

        # Calculate maintainability index
        import math

        if metrics.code_lines > 0:
            # Adjusted for C#
            async_factor = 1 - (metrics.async_methods * 0.01)
            unity_factor = 1 - (getattr(metrics, "coroutines", 0) * 0.02)

            mi = (
                171
                - 5.2 * math.log(max(1, complexity))
                - 0.23 * complexity
                - 16.2 * math.log(metrics.code_lines)
                + 10 * async_factor
                + 10 * unity_factor
            )
            metrics.maintainability_index = max(0, min(100, mi))

    return metrics

CSSAnalyzer

Python
CSSAnalyzer()

Bases: LanguageAnalyzer

CSS code analyzer with preprocessor and framework support.

Provides comprehensive analysis for CSS files including: - CSS3 features and properties - SCSS/Sass preprocessor features - Less preprocessor features - PostCSS plugins and features - Tailwind CSS utility classes - UnoCSS atomic CSS - CSS-in-JS patterns - CSS Modules - BEM, OOCSS, SMACSS methodologies - Performance metrics - Browser compatibility - Accessibility considerations - Design system patterns

Supports modern CSS development practices and frameworks.

Initialize the CSS analyzer with logger.

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def __init__(self):
    """Initialize the CSS analyzer with logger."""
    self.logger = get_logger(__name__)

    # Tailwind utility patterns
    self.tailwind_patterns = self._load_tailwind_patterns()

    # UnoCSS patterns
    self.unocss_patterns = self._load_unocss_patterns()

    # CSS framework patterns
    self.framework_patterns = self._load_framework_patterns()

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract import statements from CSS.

Handles: - @import statements - @use (Sass) - @forward (Sass) - url() functions - CSS Modules composes

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract import statements from CSS.

    Handles:
    - @import statements
    - @use (Sass)
    - @forward (Sass)
    - url() functions
    - CSS Modules composes

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []

    # Determine file type
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    is_less = ext == ".less"

    # @import statements
    import_pattern = r'@import\s+(?:url\()?["\']([^"\']+)["\'](?:\))?(?:\s+([^;]+))?;'
    for match in re.finditer(import_pattern, content):
        import_path = match.group(1)
        media_query = match.group(2)

        imports.append(
            ImportInfo(
                module=import_path,
                line=content[: match.start()].count("\n") + 1,
                type="import",
                is_relative=not import_path.startswith(("http://", "https://", "//")),
                media_query=media_query.strip() if media_query else None,
                category=self._categorize_css_import(import_path),
            )
        )

    # @use statements (Sass)
    if is_scss:
        use_pattern = r'@use\s+["\']([^"\']+)["\'](?:\s+as\s+(\w+))?(?:\s+with\s*\(([^)]+)\))?;'
        for match in re.finditer(use_pattern, content):
            module_path = match.group(1)
            namespace = match.group(2)
            config = match.group(3)

            imports.append(
                ImportInfo(
                    module=module_path,
                    line=content[: match.start()].count("\n") + 1,
                    type="use",
                    is_relative=not module_path.startswith(("http://", "https://", "//")),
                    namespace=namespace,
                    config=config,
                    category=self._categorize_css_import(module_path),
                )
            )

        # @forward statements (Sass)
        forward_pattern = r'@forward\s+["\']([^"\']+)["\'](?:\s+(show|hide)\s+([^;]+))?;'
        for match in re.finditer(forward_pattern, content):
            module_path = match.group(1)
            visibility_type = match.group(2)
            visibility_items = match.group(3)

            # Combine visibility type and items for easier testing
            if visibility_type and visibility_items:
                visibility = f"{visibility_type} {visibility_items.strip()}"
            else:
                visibility = None

            imports.append(
                ImportInfo(
                    module=module_path,
                    line=content[: match.start()].count("\n") + 1,
                    type="forward",
                    is_relative=not module_path.startswith(("http://", "https://", "//")),
                    visibility=visibility,
                    category=self._categorize_css_import(module_path),
                )
            )

    # url() in properties (for fonts, images, etc.)
    url_pattern = r'url\(["\']?([^"\')\s]+)["\']?\)'
    for match in re.finditer(url_pattern, content):
        url_path = match.group(1)

        # Skip data URLs and already imported files
        if url_path.startswith("data:") or any(imp.module == url_path for imp in imports):
            continue

        imports.append(
            ImportInfo(
                module=url_path,
                line=content[: match.start()].count("\n") + 1,
                type="url",
                is_relative=not url_path.startswith(("http://", "https://", "//")),
                category=self._categorize_url_import(url_path),
            )
        )

    # CSS Modules composes
    composes_pattern = r'composes:\s*([a-zA-Z0-9-_\s]+)\s+from\s+["\']([^"\']+)["\'];'
    for match in re.finditer(composes_pattern, content):
        classes = match.group(1)
        module_path = match.group(2)

        imports.append(
            ImportInfo(
                module=module_path,
                line=content[: match.start()].count("\n") + 1,
                type="composes",
                is_relative=not module_path.startswith(("http://", "https://", "//")),
                composes=classes.strip(),
                # Alias for tests that expect composed_classes
                visibility=None,
                category="css_module",
            )
        )
    # Backward compatibility: also attach composed_classes attribute dynamically
    for imp in imports:
        if imp.type == "composes" and getattr(imp, "composes", None):
            # Some tests reference ImportInfo.composed_classes
            try:
                setattr(imp, "composed_classes", imp.composes)
            except Exception:
                pass

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported elements from CSS.

In CSS context, exports are: - Classes that can be used by HTML - IDs - Custom properties (CSS variables) - Mixins (SCSS/Less) - Functions (SCSS) - Keyframe animations - Utility classes (Tailwind/UnoCSS)

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported elements

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported elements from CSS.

    In CSS context, exports are:
    - Classes that can be used by HTML
    - IDs
    - Custom properties (CSS variables)
    - Mixins (SCSS/Less)
    - Functions (SCSS)
    - Keyframe animations
    - Utility classes (Tailwind/UnoCSS)

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported elements
    """
    exports = []

    # Parse CSS
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    parser = CSSParser(content, is_scss)
    parser.parse()

    # Export CSS classes (from selectors only)
    classes: Set[str] = set()
    for rule in parser.rules:
        selector = rule.get("selector", "")
        for match in re.finditer(r"\.([a-zA-Z0-9_\\:-]+)", selector):
            class_name = match.group(1)
            if class_name not in classes:
                classes.add(class_name)
                pos = content.find("." + class_name)
                exports.append(
                    {
                        "name": class_name,
                        "type": "class",
                        "line": (content[:pos].count("\n") + 1) if pos != -1 else None,
                    }
                )

    # Export IDs (from selectors only, avoid hex colors)
    ids: Set[str] = set()
    for rule in parser.rules:
        selector = rule.get("selector", "")
        for match in re.finditer(r"#([a-zA-Z0-9_-]+)", selector):
            id_name = match.group(1)
            if id_name not in ids:
                ids.add(id_name)
                pos = content.find("#" + id_name)
                exports.append(
                    {
                        "name": id_name,
                        "type": "id",
                        "line": (content[:pos].count("\n") + 1) if pos != -1 else None,
                    }
                )

    # Export custom properties
    for prop_name, prop_value in parser.custom_properties.items():
        exports.append(
            {
                "name": prop_name,
                "type": "custom_property",
                "value": prop_value,
            }
        )

    # Export SCSS variables, mixins, functions
    if is_scss:
        for var_name, var_value in parser.variables.items():
            exports.append(
                {
                    "name": var_name,
                    "type": "scss_variable",
                    "value": var_value,
                }
            )
        for mixin in parser.mixins:
            exports.append(
                {
                    "name": mixin["name"],
                    "type": "mixin",
                    "params": mixin["params"],
                }
            )
        for func in parser.functions:
            exports.append(
                {
                    "name": func["name"],
                    "type": "function",
                    "params": func["params"],
                }
            )

    # Export keyframes
    for keyframe in parser.keyframes:
        exports.append(
            {
                "name": keyframe["name"],
                "type": "keyframe",
            }
        )

    # Export utility classes (Tailwind/UnoCSS)
    if self._is_utility_css(content):
        utility_classes = self._extract_utility_classes(content)
        for util_class in utility_classes:
            exports.append(
                {
                    "name": util_class,
                    "type": "utility_class",
                    "framework": self._detect_utility_framework(content),
                }
            )

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract CSS document structure.

Extracts: - Rules and selectors - Media queries - CSS architecture patterns - Framework usage - Design tokens - Component structure

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract CSS document structure.

    Extracts:
    - Rules and selectors
    - Media queries
    - CSS architecture patterns
    - Framework usage
    - Design tokens
    - Component structure

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Parse CSS
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    is_less = ext == ".less"
    parser = CSSParser(content, is_scss)
    parser.parse()

    # Store parsed data
    structure.rules = parser.rules
    structure.variables = parser.variables
    structure.custom_properties = parser.custom_properties
    structure.mixins = parser.mixins
    structure.functions = parser.functions
    structure.keyframes = parser.keyframes
    structure.media_queries = parser.media_queries
    structure.supports_rules = parser.supports_rules
    structure.max_nesting = parser.max_nesting

    # Detect CSS methodology
    structure.uses_bem = self._detect_bem(content)
    structure.uses_oocss = self._detect_oocss(content)
    structure.uses_smacss = self._detect_smacss(content)
    structure.uses_atomic = self._detect_atomic_css(content)

    # Detect frameworks
    structure.is_tailwind = self._detect_tailwind(content, file_path)
    structure.is_unocss = self._detect_unocss(content, file_path)
    structure.is_bootstrap = self._detect_bootstrap(content)
    structure.is_bulma = self._detect_bulma(content)
    structure.is_material = self._detect_material(content)

    # Count selectors by type (from selectors only)
    selectors_joined = ",".join(rule.get("selector", "") for rule in parser.rules)
    structure.element_selectors = len(
        re.findall(r"(?:(?<=^)|(?<=[\s>+~,(]))[a-zA-Z][a-zA-Z0-9-]*", selectors_joined)
    )
    structure.class_selectors = len(re.findall(r"\.[a-zA-Z0-9_\\:-]+", selectors_joined))
    structure.id_selectors = len(re.findall(r"#[a-zA-Z0-9_-]+", selectors_joined))
    structure.attribute_selectors = len(re.findall(r"\[[^\]]+\]", selectors_joined))
    structure.pseudo_classes = len(re.findall(r":(?!:)[a-z-]+(?:\([^)]*\))?", selectors_joined))
    structure.pseudo_elements = len(re.findall(r"::[a-z-]+", selectors_joined))

    # Count CSS3 features
    structure.flexbox_usage = len(re.findall(r"display\s*:\s*(?:inline-)?flex", content))
    structure.grid_usage = len(re.findall(r"display\s*:\s*grid", content))
    structure.custom_property_usage = len(re.findall(r"var\(--[^)]+\)", content))
    structure.calc_usage = len(re.findall(r"calc\([^)]+\)", content))
    structure.transform_usage = len(re.findall(r"transform\s*:", content))
    structure.transition_usage = len(re.findall(r"transition\s*:", content))
    structure.animation_usage = len(re.findall(r"animation\s*:", content))

    # Count responsive features
    structure.media_query_count = len(parser.media_queries)
    structure.viewport_units = len(re.findall(r"\d+(?:vw|vh|vmin|vmax)\b", content))
    structure.container_queries = len(re.findall(r"@container\s+", content))

    # Count modern CSS features
    structure.css_nesting = len(
        re.findall(r"&\s*[{:.]", content)
    ) + self._count_nested_selectors(content)
    structure.has_layers = bool(re.search(r"@layer\s+", content))
    structure.has_cascade_layers = len(re.findall(r"@layer\s+[a-z-]+\s*[{,]", content))

    # Design system detection
    structure.has_design_tokens = self._detect_design_tokens(content)
    structure.color_variables = self._count_color_variables(parser.custom_properties)
    structure.spacing_variables = self._count_spacing_variables(parser.custom_properties)
    structure.typography_variables = self._count_typography_variables(parser.custom_properties)

    # Component-based structure
    structure.component_count = self._count_components(content)
    structure.utility_count = self._count_utilities(content)

    # PostCSS features
    structure.uses_postcss = self._detect_postcss(content, file_path)
    structure.postcss_plugins = self._detect_postcss_plugins(content)

    # CSS-in-JS patterns
    structure.is_css_modules = self._detect_css_modules(content, file_path)
    structure.is_styled_components = self._detect_styled_components(content)

    # Performance indicators
    structure.unused_variables = self._find_unused_variables(content, parser)
    structure.duplicate_properties = self._find_duplicate_properties(parser.rules)
    structure.vendor_prefixes = len(re.findall(r"-(?:webkit|moz|ms|o)-", content))

    # Accessibility
    structure.focus_styles = len(re.findall(r":focus\s*[{,]", content))
    structure.focus_visible = len(re.findall(r":focus-visible\s*[{,]", content))
    structure.reduced_motion = len(re.findall(r"prefers-reduced-motion", content))
    structure.high_contrast = len(re.findall(r"prefers-contrast", content))
    structure.color_scheme = len(re.findall(r"prefers-color-scheme", content))

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for CSS.

Calculates: - Selector complexity - Specificity metrics - Rule complexity - Nesting depth - Framework complexity - Performance score - Maintainability index

PARAMETERDESCRIPTION
content

CSS source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/css_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for CSS.

    Calculates:
    - Selector complexity
    - Specificity metrics
    - Rule complexity
    - Nesting depth
    - Framework complexity
    - Performance score
    - Maintainability index

    Args:
        content: CSS source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Parse CSS
    ext = file_path.suffix.lower()
    is_scss = ext in [".scss", ".sass"]
    parser = CSSParser(content, is_scss)
    parser.parse()

    # Basic metrics
    lines = content.split("\n")
    metrics.line_count = len(lines)
    metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
    metrics.comment_lines = len(re.findall(r"/\*.*?\*/", content, re.DOTALL))
    if is_scss:
        metrics.comment_lines += len([l for l in lines if l.strip().startswith("//")])

    # Rule metrics
    metrics.total_rules = len(parser.rules)
    metrics.total_selectors = sum(len(rule["selector"].split(",")) for rule in parser.rules)

    # Calculate average specificity
    total_specificity = [0, 0, 0]
    max_specificity = [0, 0, 0]

    for rule in parser.rules:
        spec = rule["specificity"]
        total_specificity[0] += spec[0]
        total_specificity[1] += spec[1]
        total_specificity[2] += spec[2]

        if spec[0] > max_specificity[0]:
            max_specificity = spec
        elif spec[0] == max_specificity[0] and spec[1] > max_specificity[1]:
            max_specificity = spec
        elif (
            spec[0] == max_specificity[0]
            and spec[1] == max_specificity[1]
            and spec[2] > max_specificity[2]
        ):
            max_specificity = spec

    if metrics.total_rules > 0:
        metrics.avg_specificity = [
            total_specificity[0] / metrics.total_rules,
            total_specificity[1] / metrics.total_rules,
            total_specificity[2] / metrics.total_rules,
        ]
    else:
        metrics.avg_specificity = [0, 0, 0]

    metrics.max_specificity = max_specificity

    # Selector complexity
    metrics.complex_selectors = 0
    metrics.overqualified_selectors = 0

    for rule in parser.rules:
        selector = rule["selector"]

        # Complex selector (too many parts)
        if len(selector.split()) > 3:
            metrics.complex_selectors += 1

        # Overqualified (element with class/id)
        if re.search(r"[a-z]+\.[a-z-]+|[a-z]+#[a-z-]+", selector, re.IGNORECASE):
            metrics.overqualified_selectors += 1

    # Important usage
    metrics.important_count = len(re.findall(r"!important", content))

    # Media query complexity
    metrics.media_query_count = len(parser.media_queries)
    metrics.media_query_complexity = sum(
        len(mq["condition"].split("and")) for mq in parser.media_queries
    )

    # Nesting depth (for SCSS)
    metrics.max_nesting_depth = parser.max_nesting

    # Color usage
    metrics.unique_colors = len(
        set(
            re.findall(
                r"#[0-9a-fA-F]{3,8}|rgb\([^)]+\)|rgba\([^)]+\)|hsl\([^)]+\)|hsla\([^)]+\)",
                content,
            )
        )
    )

    # Font usage
    metrics.unique_fonts = len(set(re.findall(r"font-family\s*:\s*([^;]+);", content)))

    # Z-index usage
    z_indices = re.findall(r"z-index\s*:\s*(-?\d+)", content)
    metrics.z_index_count = len(z_indices)
    if z_indices:
        metrics.max_z_index = max(int(z) for z in z_indices)
    else:
        metrics.max_z_index = 0

    # File size metrics
    metrics.file_size = len(content.encode("utf-8"))
    metrics.gzip_ratio = self._estimate_gzip_ratio(content)

    # Framework-specific metrics
    if self._detect_tailwind(content, file_path):
        metrics.tailwind_classes = self._count_tailwind_classes(content)
        metrics.custom_utilities = self._count_custom_utilities(content)

    if self._detect_unocss(content, file_path):
        metrics.unocss_classes = self._count_unocss_classes(content)

    # Calculate CSS complexity score
    complexity_score = (
        metrics.total_rules * 0.1
        + metrics.complex_selectors * 2
        + metrics.overqualified_selectors * 1.5
        + metrics.important_count * 3
        + metrics.max_nesting_depth * 1
        + (metrics.max_specificity[0] * 10)  # IDs weighted heavily
        + (metrics.max_specificity[1] * 2)  # Classes
        + (metrics.max_specificity[2] * 0.5)  # Elements
    )
    metrics.complexity_score = complexity_score

    # Performance score
    performance_score = 100

    # Deduct for complexity
    performance_score -= min(30, complexity_score / 10)

    # Deduct for !important
    performance_score -= min(20, metrics.important_count * 2)

    # Deduct for deep nesting
    performance_score -= min(10, metrics.max_nesting_depth * 2)

    # Deduct for excessive specificity
    performance_score -= min(10, metrics.max_specificity[0] * 5)

    # Bonus for CSS variables usage
    if len(parser.custom_properties) > 0:
        performance_score += min(10, len(parser.custom_properties) * 0.5)

    metrics.performance_score = max(0, performance_score)

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Factors affecting CSS maintainability
        specificity_factor = 1 - (sum(metrics.avg_specificity) * 0.1)
        important_factor = 1 - (metrics.important_count * 0.02)
        nesting_factor = 1 - (metrics.max_nesting_depth * 0.05)
        organization_factor = 1 if len(parser.custom_properties) > 0 else 0.8

        mi = (
            171
            - 5.2 * math.log(max(1, metrics.total_rules))
            - 0.23 * complexity_score
            - 16.2 * math.log(max(1, metrics.code_lines))
            + 20 * specificity_factor
            + 10 * important_factor
            + 10 * nesting_factor
            + 10 * organization_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))
    else:
        metrics.maintainability_index = 100

    return metrics

DartAnalyzer

Python
DartAnalyzer()

Bases: LanguageAnalyzer

Dart code analyzer with Flutter support.

Provides comprehensive analysis for Dart files including: - Import and export directives - Part and library declarations - Classes with mixins and extensions - Null safety features (?, !, late) - Async/await, Future, and Stream handling - Flutter widgets and lifecycle methods - Factory and named constructors - Extension methods - Annotations and metadata - Generics and type parameters

Supports Dart 2.x with null safety and Flutter framework patterns.

Initialize the Dart analyzer with logger.

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def __init__(self):
    """Initialize the Dart analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract import, export, part, and library directives from Dart code.

Handles: - import statements: import 'package:flutter/material.dart'; - export statements: export 'src/widget.dart'; - part statements: part 'implementation.dart'; - part of statements: part of 'library.dart'; - library declarations: library my_library; - Conditional imports: import 'stub.dart' if (dart.library.io) 'io.dart'; - Show/hide clauses: import 'dart:math' show Random hide PI; - Deferred imports: import 'big_lib.dart' deferred as big;

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract import, export, part, and library directives from Dart code.

    Handles:
    - import statements: import 'package:flutter/material.dart';
    - export statements: export 'src/widget.dart';
    - part statements: part 'implementation.dart';
    - part of statements: part of 'library.dart';
    - library declarations: library my_library;
    - Conditional imports: import 'stub.dart' if (dart.library.io) 'io.dart';
    - Show/hide clauses: import 'dart:math' show Random hide PI;
    - Deferred imports: import 'big_lib.dart' deferred as big;

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []
    lines = content.split("\n")

    for i, line in enumerate(lines, 1):
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Import statements - handle more complex patterns with show/hide
        # First, try to extract the basic import and parse show/hide separately
        basic_import_pattern = r"^\s*import\s+['\"]([^'\"]+)['\"](?:\s+if\s*\([^)]+\)\s*['\"][^'\"]+['\"]*)?(?:\s+deferred)?(?:\s+as\s+(\w+))?(.*?);"
        match = re.match(basic_import_pattern, line)
        if match:
            module_path = match.group(1)
            alias = match.group(2)
            show_hide_part = match.group(3) if match.group(3) else ""

            # Parse show/hide clauses
            show_symbols = []
            hide_symbols = []
            is_deferred = "deferred" in line

            # Extract show clause
            show_match = re.search(r"\bshow\s+([^;]+?)(?:\s+hide|$)", show_hide_part + " ")
            if show_match:
                show_symbols = self._parse_symbols(show_match.group(1))

            # Extract hide clause
            hide_match = re.search(r"\bhide\s+([^;]+?)(?:\s+show|$)", show_hide_part + " ")
            if hide_match:
                hide_symbols = self._parse_symbols(hide_match.group(1))

            # Determine import type
            import_type = "import"
            is_package = module_path.startswith("package:")
            is_dart_core = module_path.startswith("dart:")
            is_relative = module_path.startswith("../") or module_path.startswith("./")

            # Categorize the import
            category = self._categorize_import(module_path)

            imports.append(
                ImportInfo(
                    module=module_path,
                    alias=alias,
                    line=i,
                    type=import_type,
                    is_relative=is_relative,
                    is_package=is_package,
                    is_dart_core=is_dart_core,
                    is_deferred=is_deferred,
                    category=category,
                    show_symbols=show_symbols if show_symbols else [],
                    hide_symbols=hide_symbols if hide_symbols else [],
                )
            )

        # Export statements
        export_pattern = r"""
            ^\s*export\s+
            ['"]([^'"]+)['"]\s*
            (?:show\s+([^;]+))?\s*
            (?:hide\s+([^;]+))?\s*
            ;
        """
        match = re.match(export_pattern, line, re.VERBOSE)
        if match:
            module_path = match.group(1)
            show_clause = match.group(2)
            hide_clause = match.group(3)

            imports.append(
                ImportInfo(
                    module=module_path,
                    line=i,
                    type="export",
                    is_relative=not module_path.startswith("package:")
                    and not module_path.startswith("dart:"),
                    show_symbols=self._parse_symbols(show_clause) if show_clause else [],
                    hide_symbols=self._parse_symbols(hide_clause) if hide_clause else [],
                    category=self._categorize_import(module_path),
                )
            )

        # Part statements
        part_pattern = r"^\s*part\s+['\"]([^'\"]+)['\"]\s*;"
        match = re.match(part_pattern, line)
        if match:
            imports.append(
                ImportInfo(
                    module=match.group(1),
                    line=i,
                    type="part",
                    is_relative=True,
                    is_part_file=True,
                )
            )

        # Part of statements
        part_of_pattern = r"^\s*part\s+of\s+['\"]?([^'\";\s]+)['\"]?\s*;"
        match = re.match(part_of_pattern, line)
        if match:
            imports.append(
                ImportInfo(
                    module=match.group(1),
                    line=i,
                    type="part_of",
                    is_relative=False,
                    is_library_part=True,
                )
            )

        # Library declaration
        library_pattern = r"^\s*library\s+(\w+(?:\.\w+)*)\s*;"
        match = re.match(library_pattern, line)
        if match:
            imports.append(
                ImportInfo(
                    module=match.group(1),
                    line=i,
                    type="library",
                    is_relative=False,
                    is_library_declaration=True,
                )
            )

    # Handle conditional, multi-line imports like:
    # import 'stub.dart'
    #   if (dart.library.io) 'io_implementation.dart'
    #   if (dart.library.html) 'web_implementation.dart';
    cond_import_pattern = (
        r"import\s+['\"]([^'\"]+)['\"]\s*(?:\s*if\s*\([^)]+\)\s*['\"][^'\"]+['\"]\s*)+;"
    )
    for m in re.finditer(cond_import_pattern, content, re.MULTILINE):
        first_module = m.group(1)
        # Avoid duplicates if already added (e.g., if written in one line)
        if not any(imp.module == first_module and imp.type == "import" for imp in imports):
            imports.append(
                ImportInfo(
                    module=first_module,
                    line=content[: m.start()].count("\n") + 1,
                    type="import",
                    is_relative=first_module.startswith("../") or first_module.startswith("./"),
                    is_package=first_module.startswith("package:"),
                    is_dart_core=first_module.startswith("dart:"),
                    category=self._categorize_import(first_module),
                    conditional=True,
                )
            )

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from Dart code.

In Dart, exports include: - Public classes (not prefixed with _) - Public functions - Public variables and constants - Public typedefs - Public enums - Extension methods

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from Dart code.

    In Dart, exports include:
    - Public classes (not prefixed with _)
    - Public functions
    - Public variables and constants
    - Public typedefs
    - Public enums
    - Extension methods

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols
    """
    exports = []

    # Public classes (including abstract and mixins)
    class_pattern = r"^\s*(?:abstract\s+)?(?:final\s+)?(?:base\s+)?(?:interface\s+)?(?:mixin\s+)?class\s+([A-Z]\w*)"
    for match in re.finditer(class_pattern, content, re.MULTILINE):
        class_name = match.group(1)

        modifiers = []
        match_str = match.group(0)
        if "abstract" in match_str:
            modifiers.append("abstract")
        if "final" in match_str:
            modifiers.append("final")
        if "base" in match_str:
            modifiers.append("base")
        if "interface" in match_str:
            modifiers.append("interface")
        if "mixin" in match_str:
            modifiers.append("mixin")

        exports.append(
            {
                "name": class_name,
                "type": "class",
                "line": content[: match.start()].count("\n") + 1,
                "modifiers": modifiers,
                "is_public": True,
            }
        )

    # Mixins
    mixin_pattern = r"^\s*(?:base\s+)?mixin\s+([A-Z]\w*)"
    for match in re.finditer(mixin_pattern, content, re.MULTILINE):
        if not any(e["name"] == match.group(1) for e in exports):  # Avoid duplicates
            exports.append(
                {
                    "name": match.group(1),
                    "type": "mixin",
                    "line": content[: match.start()].count("\n") + 1,
                    "is_public": True,
                }
            )

    # Public functions (not starting with _), including async*, sync*
    func_pattern = r"^\s*(?:Future<?[^>]*>?\s+|Stream<?[^>]*>?\s+|void\s+|[\w<>]+\s+)?([a-z]\w*)\s*(?:<[^>]+>)?\s*\([^\{]*\)\s*(?:(?:async|sync)\s*\*|async)?\s*(?:=>|\{)"
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        if not func_name.startswith("_"):
            snippet = match.group(0)
            exports.append(
                {
                    "name": func_name,
                    "type": "function",
                    "line": content[: match.start()].count("\n") + 1,
                    "is_public": True,
                    "is_async": ("async" in snippet),
                }
            )

    # Public variables and constants
    var_pattern = r"^\s*(?:final\s+|const\s+|late\s+)?(?:static\s+)?(?:final\s+|const\s+)?(?:[\w<>?]+\s+)?([a-z]\w*)\s*(?:=|;)"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        if not var_name.startswith("_") and var_name not in [
            "if",
            "for",
            "while",
            "return",
            "class",
            "import",
        ]:
            var_type = "constant" if "const" in match.group(0) else "variable"
            exports.append(
                {
                    "name": var_name,
                    "type": var_type,
                    "line": content[: match.start()].count("\n") + 1,
                    "is_public": True,
                    "is_final": "final" in match.group(0),
                    "is_late": "late" in match.group(0),
                }
            )

    # Enums
    enum_pattern = r"^\s*enum\s+([A-Z]\w*)"
    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "enum",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
            }
        )

    # Typedefs
    typedef_pattern = r"^\s*typedef\s+([A-Z]\w*)"
    for match in re.finditer(typedef_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "typedef",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
            }
        )

    # Extension methods
    extension_pattern = r"^\s*extension\s+(?:([A-Z]\w*)\s+)?on\s+([A-Z]\w*)"
    for match in re.finditer(extension_pattern, content, re.MULTILINE):
        extension_name = match.group(1) or f"Extension on {match.group(2)}"
        exports.append(
            {
                "name": extension_name,
                "type": "extension",
                "line": content[: match.start()].count("\n") + 1,
                "on_type": match.group(2),
                "is_public": True,
            }
        )

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from Dart file.

Extracts: - Classes with inheritance, mixins, and interfaces - Constructors (default, named, factory) - Methods and getters/setters - Flutter widgets and lifecycle methods - Async functions and streams - Extension methods - Null safety features - Annotations

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from Dart file.

    Extracts:
    - Classes with inheritance, mixins, and interfaces
    - Constructors (default, named, factory)
    - Methods and getters/setters
    - Flutter widgets and lifecycle methods
    - Async functions and streams
    - Extension methods
    - Null safety features
    - Annotations

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Detect if it's a Flutter file
    structure.is_flutter = self._is_flutter_file(content)

    # Extract classes
    class_pattern = r"""
        ^\s*(?:@\w+(?:\([^)]*\))?\s*)*  # Annotations
        (?:(abstract)\s+)?
        (?:(final)\s+)?
        (?:(base)\s+)?
        (?:(interface)\s+)?
        (?:(mixin)\s+)?
        (?:(sealed)\s+)?
        class\s+(\w+)
        (?:<([^>\n{}]*?)>+)?  # Generics (tolerant of nested '>')
        (?:\s+extends\s+([^\{]+?))?
        (?:\s+with\s+([^\{]+?))?
        (?:\s+implements\s+([^\{]+?))?
        \s*\{
    """

    for match in re.finditer(class_pattern, content, re.VERBOSE | re.MULTILINE):
        class_name = match.group(7)

        # Extract class modifiers
        modifiers = []
        if match.group(1):
            modifiers.append("abstract")
        if match.group(2):
            modifiers.append("final")
        if match.group(3):
            modifiers.append("base")
        if match.group(4):
            modifiers.append("interface")
        if match.group(5):
            modifiers.append("mixin")
        if match.group(6):
            modifiers.append("sealed")

        # Parse inheritance
        extends = match.group(9).strip() if match.group(9) else None
        mixins = self._parse_type_list(match.group(10)) if match.group(10) else []
        implements = self._parse_type_list(match.group(11)) if match.group(11) else []

        # Check if it's a Flutter widget
        is_widget = False
        widget_type = None
        if extends:
            # Prefer concrete State<T> first to avoid misclassification
            if re.search(r"\bState<", extends):
                is_widget = True
                widget_type = "state"
            elif "StatelessWidget" in extends:
                is_widget = True
                widget_type = "stateless"
            elif "StatefulWidget" in extends:
                is_widget = True
                widget_type = "stateful"
            elif "InheritedWidget" in extends:
                is_widget = True
                widget_type = "inherited"

        # Extract class body
        class_body = self._extract_class_body(content, match.end())

        if class_body:
            # Extract constructors
            constructors = self._extract_constructors(class_body, class_name)

            # Extract methods
            methods = self._extract_methods(class_body)

            # Extract fields
            fields = self._extract_fields(class_body)

            # Extract getters/setters
            properties = self._extract_properties(class_body)
        else:
            constructors = []
            methods = []
            fields = []
            properties = []

        class_info = ClassInfo(
            name=class_name,
            line=content[: match.start()].count("\n") + 1,
            modifiers=modifiers,
            generics=match.group(8),
            bases=[extends] if extends else [],
            mixins=mixins,
            interfaces=implements,
            constructors=constructors,
            methods=methods,
            fields=fields,
            properties=properties,
            is_widget=is_widget,
            widget_type=widget_type,
            is_sealed="sealed" in modifiers,
        )

        # Balance generics angle brackets if regex captured incomplete nested generics
        if class_info.generics:
            try:
                opens = class_info.generics.count("<")
                closes = class_info.generics.count(">")
                if opens > closes:
                    class_info.generics = class_info.generics + (">" * (opens - closes))
            except Exception:
                pass

        structure.classes.append(class_info)

    # Fallback: capture classes with complex generic bounds that the primary regex may miss
    try:
        existing = {c.name for c in structure.classes}
        complex_class_pattern = r"""^\s*
            (?:(abstract|final|base|interface|mixin|sealed)\s+)*
            class\s+(\w+)\s*<([^\n{]+)>\s*
            (?:extends\s+([^\n{]+?))?\s*
            (?:with\s+([^\n{]+?))?\s*
            (?:implements\s+([^\n{]+?))?\s*\{
        """
        for m in re.finditer(complex_class_pattern, content, re.MULTILINE | re.VERBOSE):
            name = m.group(2)
            if name in existing:
                continue
            modifiers_raw = m.group(1) or ""
            modifiers = [mod for mod in modifiers_raw.split() if mod]
            generics = m.group(3).strip()
            extends = m.group(4).strip() if m.group(4) else None
            mixins = self._parse_type_list(m.group(5)) if m.group(5) else []
            implements = self._parse_type_list(m.group(6)) if m.group(6) else []
            structure.classes.append(
                ClassInfo(
                    name=name,
                    line=content[: m.start()].count("\n") + 1,
                    generics=generics,
                    bases=[extends] if extends else [],
                    mixins=mixins,
                    interfaces=implements,
                    constructors=[],
                    methods=[],
                    fields=[],
                    properties=[],
                    modifiers=modifiers,
                )
            )
    except Exception:
        pass

    # Extract mixins (standalone)
    mixin_pattern = r"^\s*(?:base\s+)?mixin\s+(\w+)(?:<([^>]+)>)?(?:\s+on\s+([^{]+))?\s*\{"
    for match in re.finditer(mixin_pattern, content, re.MULTILINE):
        mixin_name = match.group(1)
        # Avoid duplicates with mixin classes
        if not any(c.name == mixin_name for c in structure.classes):
            structure.mixins.append(
                {
                    "name": mixin_name,
                    "line": content[: match.start()].count("\n") + 1,
                    "generics": match.group(2),
                    "on_types": self._parse_type_list(match.group(3)) if match.group(3) else [],
                }
            )

    # Extract top-level functions
    func_pattern = r"""
        ^\s*(?:@\w+(?:\([^)]*\))?\s*)*  # Annotations
        (?:(Future|Stream)(?:<[^>]+>)?\s+)?
        (?:(void|[\w<>?]+|\([^)]+\))\s+)?  # Return type or record type
        ([a-zA-Z_]\w*)\s*
        (?:<[^>]+>)?\s*  # Generic parameters
        \(([^)]*)\)\s*
        (?:(?:async|sync)\s*\*|async)?\s*  # async, async*, or sync*
        (?:=>|\{)
    """

    for match in re.finditer(func_pattern, content, re.VERBOSE | re.MULTILINE):
        func_name = match.group(3)
        # Skip if it's inside a class
        if not self._is_top_level(content, match.start()):
            continue

        return_type = match.group(1) or match.group(2)
        params = match.group(4)

        span = content[match.start() : match.end()]
        is_async = "async" in span
        is_generator = "*" in span

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            return_type=return_type,
            parameters=self._parse_parameters(params),
            is_async=is_async,
            is_generator=is_generator,
            is_private=func_name.startswith("_"),
        )

        structure.functions.append(func_info)

    # Extract enums (brace-aware, supports enhanced enums with methods)
    enum_head_pattern = r"^\s*enum\s+(\w+)(?:\s*<[^>]+>)?(?:\s+implements\s+[^\{]+)?\s*\{"
    for m in re.finditer(enum_head_pattern, content, re.MULTILINE):
        enum_name = m.group(1)
        enum_body = self._extract_block(content, m.end()) or ""
        if enum_body is None:
            continue
        # Determine the values section: up to first top-level ';' if present
        values_part = enum_body
        depth = 0
        cutoff = None
        for i, ch in enumerate(enum_body):
            if ch == "{":
                depth += 1
            elif ch == "}":
                depth = max(0, depth - 1)
            elif ch == ";" and depth == 0:
                cutoff = i
                break
        if cutoff is not None:
            values_part = enum_body[:cutoff]
        values = self._parse_enum_values(values_part)
        structure.enums.append(
            {
                "name": enum_name,
                "line": content[: m.start()].count("\n") + 1,
                "values": values,
                "has_enhanced_features": ("(" in values_part) or (cutoff is not None),
            }
        )

    # Extract extensions
    extension_pattern = r"^\s*extension\s+(?:(\w+)\s+)?on\s+([^\{]+)\s*\{"
    for match in re.finditer(extension_pattern, content, re.MULTILINE):
        extension_name = match.group(1) or f"on {match.group(2)}"
        on_type = match.group(2).strip()

        structure.extensions.append(
            {
                "name": extension_name,
                "line": content[: match.start()].count("\n") + 1,
                "on_type": on_type,
            }
        )

    # Extract typedefs
    typedef_pattern = r"^\s*typedef\s+(\w+)(?:<[^>]+>)?\s*=\s*([^;]+);"
    for match in re.finditer(typedef_pattern, content, re.MULTILINE):
        structure.typedefs.append(
            {
                "name": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
                "definition": match.group(2).strip(),
            }
        )

    # Count null safety features
    structure.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\))", content))
    structure.null_assertions = len(re.findall(r"\w+!(?:\.|;|\s|\))", content))
    structure.late_variables = len(re.findall(r"\blate\s+", content))
    structure.null_aware_operators = len(re.findall(r"\?\?|\?\.", content))

    # Count async features
    structure.async_functions = len(re.findall(r"\basync\s*(?:\*)?\s*(?:=>|\{)", content))
    structure.await_expressions = len(re.findall(r"\bawait\s+", content))
    structure.future_count = len(re.findall(r"\bFuture(?:\s*<|[.(])", content))
    structure.stream_count = len(re.findall(r"\bStream(?:\s*<|[.(])", content))

    # Detect test file
    structure.is_test_file = (
        "_test.dart" in file_path.name or file_path.parts and "test" in file_path.parts
    )

    # Detect main function
    structure.has_main = bool(re.search(r"\bvoid\s+main\s*\(", content))

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for Dart code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Null safety complexity - Async complexity - Flutter-specific complexity - Class hierarchy depth

PARAMETERDESCRIPTION
content

Dart source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/dart_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for Dart code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Null safety complexity
    - Async complexity
    - Flutter-specific complexity
    - Class hierarchy depth

    Args:
        content: Dart source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bcatch\b",
        r"\b\?\s*[^:]+\s*:",  # Ternary operator
        r"\?\?",  # Null coalescing
        r"&&",
        r"\|\|",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\belse\s+if\b", 1),
            (r"\belse\b", 0),
            (r"\bfor\b", 1),
            (r"\bwhile\b", 1),
            (r"\bdo\b", 1),
            (r"\bswitch\b", 1),
            (r"\btry\b", 1),
            (r"\bcatch\b", 1),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("//")])
    metrics.comment_lines = len([l for l in lines if l.strip().startswith("//")])
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count classes and methods
    metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
    metrics.mixin_count = len(re.findall(r"\bmixin\s+\w+", content))
    metrics.method_count = len(
        re.findall(
            r"(?:^|\s)(?:Future|Stream|void|[\w<>]+)\s+\w+\s*\([^)]*\)\s*(?:async\s*)?(?:=>|\{)",
            content,
        )
    )

    # Null safety metrics
    metrics.nullable_types = len(re.findall(r"\w+\?(?:\s|,|\))", content))
    metrics.null_assertions = len(re.findall(r"\w+!(?:\.|;|\s|\))", content))
    metrics.late_keywords = len(re.findall(r"\blate\s+", content))
    metrics.null_aware_ops = len(re.findall(r"\?\?|\?\.|\?\.\?", content))
    metrics.required_keywords = len(re.findall(r"\brequired\s+", content))

    # Async metrics
    metrics.async_functions = len(re.findall(r"\basync\s*(?:\*)?\s*(?:=>|\{)", content))
    metrics.await_count = len(re.findall(r"\bawait\s+", content))
    metrics.future_count = len(re.findall(r"\bFuture(?:\s*<|[.(])", content))
    metrics.stream_count = len(re.findall(r"\bStream(?:\s*<|[.(])", content))
    metrics.completer_count = len(re.findall(r"\bCompleter<", content))

    # Flutter-specific metrics
    if self._is_flutter_file(content):
        metrics.widget_count = len(re.findall(r"\bWidget\b", content))
        metrics.build_methods = len(re.findall(r"\bWidget\s+build\s*\(", content))
        metrics.setstate_calls = len(re.findall(r"\bsetState\s*\(", content))
        metrics.stateful_widgets = len(re.findall(r"extends\s+StatefulWidget", content))
        metrics.stateless_widgets = len(re.findall(r"extends\s+StatelessWidget", content))
        metrics.inherited_widgets = len(re.findall(r"extends\s+InheritedWidget", content))

        # Flutter hooks and keys
        metrics.keys_used = len(
            re.findall(r"\bKey\s*\(|GlobalKey|ValueKey|ObjectKey|UniqueKey", content)
        )
        metrics.context_usage = len(re.findall(r"\bBuildContext\b", content))

    # Exception handling metrics
    metrics.try_blocks = len(re.findall(r"\btry\s*\{", content))
    metrics.catch_blocks = len(re.findall(r"\bcatch\s*\(", content))
    metrics.finally_blocks = len(re.findall(r"\bfinally\s*\{", content))
    metrics.throw_statements = len(re.findall(r"\bthrow\s+", content))
    metrics.rethrow_statements = len(re.findall(r"\brethrow\s*;", content))

    # Type system metrics
    metrics.generic_types = len(re.findall(r"<[\w\s,<>]+>", content))
    metrics.type_parameters = len(re.findall(r"<\w+(?:\s+extends\s+\w+)?>", content))
    metrics.dynamic_types = len(re.findall(r"\bdynamic\b", content))
    metrics.var_declarations = len(re.findall(r"\bvar\s+\w+", content))

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Adjusted for Dart
        null_safety_factor = 1 - (metrics.null_assertions * 0.01)
        async_factor = 1 - (metrics.async_functions * 0.01)
        flutter_factor = (
            1 - (metrics.setstate_calls * 0.02) if hasattr(metrics, "setstate_calls") else 1
        )
        type_factor = 1 + ((metrics.nullable_types - metrics.dynamic_types) * 0.001)

        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 10 * null_safety_factor
            + 5 * async_factor
            + 5 * flutter_factor
            + 5 * type_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

GDScriptAnalyzer

Python
GDScriptAnalyzer()

Bases: LanguageAnalyzer

GDScript code analyzer for Godot development.

Provides comprehensive analysis for GDScript files including: - Preload and load statements - Class inheritance (extends) - Signal declarations and connections - Export variable declarations - Onready variables and node references - Godot lifecycle methods (_ready, _process, etc.) - Tool scripts and custom resources - Typed GDScript (static typing) - Inner classes - Setget properties - Remote and master/puppet keywords (networking)

Supports Godot 3.x and 4.x GDScript syntax.

Initialize the GDScript analyzer with logger.

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def __init__(self):
    """Initialize the GDScript analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract preload, load, and class references from GDScript code.

Handles: - preload statements: preload("res://path/to/script.gd") - load statements: load("res://path/to/resource.tres") - const preloads: const MyClass = preload("res://MyClass.gd") - class_name declarations (Godot 3.1+) - Tool script declarations

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract preload, load, and class references from GDScript code.

    Handles:
    - preload statements: preload("res://path/to/script.gd")
    - load statements: load("res://path/to/resource.tres")
    - const preloads: const MyClass = preload("res://MyClass.gd")
    - class_name declarations (Godot 3.1+)
    - Tool script declarations

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []
    lines = content.split("\n")

    for i, line in enumerate(lines, 1):
        # Skip comments
        if line.strip().startswith("#"):
            continue

        # Preload statements
        preload_pattern = r'(?:const\s+)?(\w+)?\s*=?\s*preload\s*\(\s*["\']([^"\']+)["\']\s*\)'
        # Use finditer to support multiple preloads on a single line and avoid overlapping matches
        for match in re.finditer(preload_pattern, line):
            const_name = match.group(1)
            resource_path = match.group(2)
            imports.append(
                ImportInfo(
                    module=resource_path,
                    alias=const_name,
                    line=i,
                    type="preload",
                    is_relative=resource_path.startswith("res://")
                    or resource_path.startswith("user://"),
                    is_resource=True,
                    resource_type=self._detect_resource_type(resource_path),
                )
            )

        # Load statements (ensure we don't match the 'load' in 'preload')
        load_pattern = r"(?<!\w)load\s*\("
        for match in re.finditer(load_pattern, line):
            # Extract the actual path argument following this 'load('
            path_match = re.search(r'\(\s*["\']([^"\']+)["\']\s*\)', line[match.start() :])
            if not path_match:
                continue
            resource_path = path_match.group(1)
            imports.append(
                ImportInfo(
                    module=resource_path,
                    line=i,
                    type="load",
                    is_relative=resource_path.startswith("res://")
                    or resource_path.startswith("user://"),
                    is_runtime_load=True,
                    resource_type=self._detect_resource_type(resource_path),
                )
            )

        # Class inheritance (extends)
        extends_pattern = r'^\s*extends\s+["\']?([^"\'\s]+)["\']?'
        match = re.match(extends_pattern, line)
        if match:
            parent_class = match.group(1)
            # Check if it's a path or class name
            is_path = "/" in parent_class or parent_class.endswith(".gd")
            imports.append(
                ImportInfo(
                    module=parent_class,
                    line=i,
                    type="extends",
                    is_relative=is_path,
                    is_inheritance=True,
                    parent_type="script" if is_path else "class",
                )
            )

        # Class_name declarations (for autoload/global classes)
        class_name_pattern = r'^\s*class_name\s+(\w+)(?:\s*,\s*["\']([^"\']+)["\'])?'
        match = re.match(class_name_pattern, line)
        if match:
            class_name = match.group(1)
            icon_path = match.group(2)
            if icon_path:
                imports.append(
                    ImportInfo(
                        module=icon_path,
                        line=i,
                        type="icon",
                        is_relative=True,
                        is_resource=True,
                        associated_class=class_name,
                    )
                )
    # Check for tool script declaration
    if re.search(r"^\s*tool\s*$", content, re.MULTILINE):
        imports.append(
            ImportInfo(
                module="@tool",
                line=1,
                type="tool_mode",
                is_relative=False,
                is_editor_script=True,
            )
        )

    # Check for @tool annotation (Godot 4.x)
    if re.search(r"^\s*@tool\s*$", content, re.MULTILINE):
        imports.append(
            ImportInfo(
                module="@tool",
                line=1,
                type="annotation",
                is_relative=False,
                is_editor_script=True,
            )
        )

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from GDScript code.

In GDScript, exports include: - class_name declarations (global classes) - export variables - signals - Public functions (by convention, non-underscore prefixed)

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from GDScript code.

    In GDScript, exports include:
    - class_name declarations (global classes)
    - export variables
    - signals
    - Public functions (by convention, non-underscore prefixed)

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols
    """
    exports = []

    # Extract class_name (makes class globally accessible)
    class_name_pattern = r'^\s*class_name\s+(\w+)(?:\s*,\s*["\']([^"\']+)["\'])?'
    match = re.search(class_name_pattern, content, re.MULTILINE)
    if match:
        exports.append(
            {
                "name": match.group(1),
                "type": "global_class",
                "line": content[: match.start()].count("\n") + 1,
                "icon": match.group(2),
                "is_autoload_candidate": True,
            }
        )

    # Extract exported variables (Godot 3.x syntax)
    export_var_pattern = r"^\s*export(?:\s*\(([^)]*)\))?\s+(?:var\s+)?(\w+)"
    for match in re.finditer(export_var_pattern, content, re.MULTILINE):
        export_type = match.group(1)
        var_name = match.group(2)

        exports.append(
            {
                "name": var_name,
                "type": "export_var",
                "line": content[: match.start()].count("\n") + 1,
                "export_type": export_type,
                "inspector_visible": True,
            }
        )

    # Extract exported variables (Godot 4.x syntax with @export)
    # Allow optional annotation arguments e.g., @export_range(0,1)
    export_annotation_pattern = r"^\s*@export(?:_([a-z_]+))?(?:\([^)]*\))?\s+(?:var\s+)?(\w+)"
    for match in re.finditer(export_annotation_pattern, content, re.MULTILINE):
        export_modifier = match.group(1)
        var_name = match.group(2)

        exports.append(
            {
                "name": var_name,
                "type": "export_var",
                "line": content[: match.start()].count("\n") + 1,
                "export_modifier": export_modifier,
                "inspector_visible": True,
                "godot_version": 4,
            }
        )

    # Extract signals
    signal_pattern = r"^\s*signal\s+(\w+)\s*(?:\(([^)]*)\))?"
    for match in re.finditer(signal_pattern, content, re.MULTILINE):
        signal_name = match.group(1)
        parameters = match.group(2)

        exports.append(
            {
                "name": signal_name,
                "type": "signal",
                "line": content[: match.start()].count("\n") + 1,
                "parameters": self._parse_signal_parameters(parameters),
                "is_event": True,
            }
        )

    # Extract public functions (non-underscore prefixed)
    func_pattern = r"^\s*(?:static\s+)?func\s+([a-zA-Z]\w*)\s*\("
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)

        exports.append(
            {
                "name": func_name,
                "type": "function",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
                "is_static": "static" in match.group(0),
            }
        )

    # Extract enums
    enum_pattern = r"^\s*enum\s+(\w+)\s*\{"
    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "enum",
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract constants (often used as exports in GDScript)
    const_pattern = r"^\s*const\s+([A-Z][A-Z0-9_]*)\s*="
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "constant",
                "line": content[: match.start()].count("\n") + 1,
                "is_public": True,
            }
        )

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from GDScript file.

Extracts: - Class inheritance and structure - Inner classes - Functions with type hints - Godot lifecycle methods - Signals and their connections - Export variables - Onready variables - Node references - Setget properties - Enums and constants

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from GDScript file.

    Extracts:
    - Class inheritance and structure
    - Inner classes
    - Functions with type hints
    - Godot lifecycle methods
    - Signals and their connections
    - Export variables
    - Onready variables
    - Node references
    - Setget properties
    - Enums and constants

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Detect if it's a tool script
    structure.is_tool_script = bool(re.search(r"^\s*(?:@)?tool\s*$", content, re.MULTILINE))

    # Extract class name
    class_name_match = re.search(r"^\s*class_name\s+(\w+)", content, re.MULTILINE)
    if class_name_match:
        structure.class_name = class_name_match.group(1)

    # Extract parent class
    extends_match = re.search(r'^\s*extends\s+["\']?([^"\'\s]+)["\']?', content, re.MULTILINE)
    if extends_match:
        structure.parent_class = extends_match.group(1)

    # Detect Godot version (4.x uses @annotations)
    structure.godot_version = (
        4 if re.search(r"^\s*@(export|onready|tool)", content, re.MULTILINE) else 3
    )

    # Extract main class info
    main_class = ClassInfo(
        name=getattr(structure, "class_name", None) or file_path.stem,
        line=1,
        bases=(
            [getattr(structure, "parent_class", None)]
            if getattr(structure, "parent_class", None)
            else []
        ),
    )

    # Extract functions
    func_pattern = r"^\s*(?:static\s+)?func\s+(\w+)\s*\(([^)]*)\)(?:\s*->\s*([^:]+))?:"
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        params = match.group(2)
        return_type = match.group(3)

        is_private = func_name.startswith("_")
        is_lifecycle = self._is_lifecycle_method(func_name)
        is_virtual = func_name.startswith("_") and not func_name.startswith("__")

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            parameters=self._parse_function_parameters(params),
            return_type=return_type.strip() if return_type else None,
            is_private=is_private,
            is_lifecycle=is_lifecycle,
            is_virtual=is_virtual,
            is_static="static" in content[match.start() - 20 : match.start()],
        )

        structure.functions.append(func_info)
        main_class.methods.append(
            {
                "name": func_name,
                "visibility": "private" if is_private else "public",
                "is_lifecycle": is_lifecycle,
            }
        )

    # Extract inner classes
    inner_class_pattern = r"^\s*class\s+(\w+)(?:\s+extends\s+([^:]+))?:"
    for match in re.finditer(inner_class_pattern, content, re.MULTILINE):
        inner_class = ClassInfo(
            name=match.group(1),
            line=content[: match.start()].count("\n") + 1,
            bases=[match.group(2).strip()] if match.group(2) else [],
            is_inner=True,
        )
        structure.classes.append(inner_class)

    # Add main class
    structure.classes.insert(0, main_class)

    # Extract signals
    signal_pattern = r"^\s*signal\s+(\w+)\s*(?:\(([^)]*)\))?"
    for match in re.finditer(signal_pattern, content, re.MULTILINE):
        structure.signals.append(
            {
                "name": match.group(1),
                "line": content[: match.start()].count("\n") + 1,
                "parameters": self._parse_signal_parameters(match.group(2)),
            }
        )

    # Extract export variables
    # Godot 3.x
    export_pattern = r"^\s*export(?:\s*\(([^)]*)\))?\s+(?:var\s+)?(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
    for match in re.finditer(export_pattern, content, re.MULTILINE):
        structure.export_vars.append(
            {
                "name": match.group(2),
                "export_hint": match.group(1),
                "type": match.group(3).strip() if match.group(3) else None,
                "default": match.group(4).strip() if match.group(4) else None,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Godot 4.x
    export_4_pattern = r"^\s*@export(?:_([a-z_]+))?(?:\([^)]*\))?\s+(?:var\s+)?(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
    for match in re.finditer(export_4_pattern, content, re.MULTILINE):
        structure.export_vars.append(
            {
                "name": match.group(2),
                "export_modifier": match.group(1),
                "type": match.group(3).strip() if match.group(3) else None,
                "default": match.group(4).strip() if match.group(4) else None,
                "line": content[: match.start()].count("\n") + 1,
                "godot_4": True,
            }
        )

    # Extract onready variables
    # Godot 3.x
    onready_pattern = r"^\s*onready\s+var\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
    for match in re.finditer(onready_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        var_type = match.group(2)
        initialization = match.group(3)

        # Check if it's a node reference
        is_node_ref = bool(re.search(r"(?:\$|get_node)", initialization))
        node_path = self._extract_node_path(initialization)

        structure.onready_vars.append(
            {
                "name": var_name,
                "type": var_type.strip() if var_type else None,
                "initialization": initialization.strip(),
                "is_node_ref": is_node_ref,
                "node_path": node_path,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Godot 4.x
    onready_4_pattern = r"^\s*@onready\s+var\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
    for match in re.finditer(onready_4_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        var_type = match.group(2)
        initialization = match.group(3)

        is_node_ref = bool(re.search(r"(?:\$|get_node)", initialization))
        node_path = self._extract_node_path(initialization)

        structure.onready_vars.append(
            {
                "name": var_name,
                "type": var_type.strip() if var_type else None,
                "initialization": initialization.strip(),
                "is_node_ref": is_node_ref,
                "node_path": node_path,
                "line": content[: match.start()].count("\n") + 1,
                "godot_4": True,
            }
        )

    # Extract regular variables
    var_pattern = r"^\s*var\s+(\w+)(?:\s*:\s*([^=\n]+))?(?:\s*=\s*([^\n]+))?"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        # Skip if it's an export or onready var
        line_start = content[: match.start()].rfind("\n") + 1
        line_content = content[line_start : match.end()]
        if "export" in line_content or "onready" in line_content or "@" in line_content:
            continue

        structure.variables.append(
            {
                "name": match.group(1),
                "type": match.group(2).strip() if match.group(2) else None,
                "initial_value": match.group(3).strip() if match.group(3) else None,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract constants
    const_pattern = r"^\s*const\s+(\w+)(?:\s*:\s*([^=\n]+))?\s*=\s*([^\n]+)"
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        structure.constants.append(
            {
                "name": match.group(1),
                "type": match.group(2).strip() if match.group(2) else None,
                "value": match.group(3).strip(),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract enums
    enum_pattern = r"^\s*enum\s+(\w+)\s*\{([^}]+)\}"
    for match in re.finditer(enum_pattern, content, re.MULTILINE):
        enum_name = match.group(1)
        enum_body = match.group(2)

        values = self._parse_enum_values(enum_body)

        structure.enums.append(
            {
                "name": enum_name,
                "values": values,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Extract setget properties
    # Support optional setter/getter and missing entries: e.g., setget set_mana or setget , get_level
    setget_pattern = r"^\s*var\s+(\w+)(?:[^=\n]*=\s*[^\n]+)?\s+setget\s*(?:([A-Za-z_]\w*)\s*)?(?:,\s*([A-Za-z_]\w*)\s*)?"
    for match in re.finditer(setget_pattern, content, re.MULTILINE):
        structure.setget_properties.append(
            {
                "name": match.group(1),
                "setter": match.group(2) if match.group(2) else None,
                "getter": match.group(3) if match.group(3) else None,
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Count node references
    structure.node_references = len(re.findall(r'\$["\']?[^"\'\s]+["\']?', content))
    structure.get_node_calls = len(re.findall(r"get_node\s*\(", content))

    # Count signal connections (method form and free function form)
    structure.connect_calls = len(re.findall(r"\.connect\s*\(|(?<!\.)\bconnect\s*\(", content))
    structure.emit_signal_calls = len(re.findall(r"emit_signal\s*\(", content))

    # Detect if it's a custom resource
    structure.is_custom_resource = bool(
        structure.parent_class and "Resource" in structure.parent_class
    )

    # Detect if it's an editor plugin
    structure.is_editor_plugin = bool(
        structure.parent_class and "EditorPlugin" in structure.parent_class
    )

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for GDScript code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Godot-specific complexity (signals, exports, node references) - Nesting depth - Function count and complexity distribution

PARAMETERDESCRIPTION
content

GDScript source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/gdscript_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for GDScript code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Godot-specific complexity (signals, exports, node references)
    - Nesting depth
    - Function count and complexity distribution

    Args:
        content: GDScript source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belif\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bmatch\b",
        r"\bwhen\b",
        r"\band\b",
        r"\bor\b",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    # Each match-case branch contributes to complexity; count simple case labels (numbers, strings, or underscore)
    case_label_pattern = r"^\s*(?:_|-?\d+|\"[^\"\n]+\"|\'[^\'\n]+\')\s*:"
    complexity += len(re.findall(case_label_pattern, content, re.MULTILINE))

    # Inline lambda expressions (func(...) :) add decision/branching potential
    lambda_inline_pattern = (
        r"func\s*\("  # named functions are 'func name(', lambdas are 'func(' directly
    )
    complexity += len(re.findall(lambda_inline_pattern, content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments
        if line.strip().startswith("#"):
            continue

        # Track nesting by indentation (GDScript uses indentation)
        if line.strip():
            indent = len(line) - len(line.lstrip())
            # Assuming tab or 4 spaces as one level
            if "\t" in line[:indent]:
                current_level = line[:indent].count("\t")
            else:
                current_level = indent // 4

            max_nesting = max(max_nesting, current_level)

            # Control structures with nesting penalty
            control_patterns = [
                (r"\bif\b", 1),
                (r"\belif\b", 1),
                (r"\belse\b", 0),
                (r"\bfor\b", 1),
                (r"\bwhile\b", 1),
                (r"\bmatch\b", 1),
            ]

            for pattern, weight in control_patterns:
                if re.search(pattern, line):
                    cognitive += weight * (1 + max(0, current_level))

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = len([l for l in lines if l.strip() and not l.strip().startswith("#")])
    metrics.comment_lines = len([l for l in lines if l.strip().startswith("#")])
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count functions
    metrics.function_count = len(re.findall(r"\bfunc\s+\w+", content))

    # Count classes
    metrics.class_count = len(re.findall(r"\bclass\s+\w+", content))
    metrics.class_count += 1 if re.search(r"^\s*extends\s+", content, re.MULTILINE) else 0

    # Godot-specific metrics
    metrics.signal_count = len(re.findall(r"\bsignal\s+\w+", content))
    metrics.export_count = len(re.findall(r"(?:@)?export(?:_\w+)?(?:\([^)]*\))?\s+", content))
    metrics.onready_count = len(re.findall(r"(?:@)?onready\s+var", content))

    # Node reference metrics
    metrics.node_ref_count = len(re.findall(r'\$["\']?[^"\'\s]+["\']?', content))
    metrics.get_node_count = len(re.findall(r"get_node\s*\(", content))

    # Signal connection metrics
    metrics.connect_count = len(re.findall(r"\.connect\s*\(|(?<!\.)\bconnect\s*\(", content))
    metrics.emit_count = len(re.findall(r"emit_signal\s*\(", content))

    # Lifecycle method count
    lifecycle_methods = [
        "_ready",
        "_enter_tree",
        "_exit_tree",
        "_process",
        "_physics_process",
        "_input",
        "_unhandled_input",
        "_draw",
        "_gui_input",
        "_notification",
    ]
    metrics.lifecycle_count = sum(
        1 for method in lifecycle_methods if re.search(rf"\bfunc\s+{method}\s*\(", content)
    )

    # RPC/Networking metrics
    metrics.rpc_count = len(
        re.findall(r"@rpc|rpc\(|rpc_unreliable\(|remotesync\s+func", content)
    )

    # Type hints metrics
    metrics.typed_vars = len(re.findall(r"(?:var|const)\s+\w+\s*:\s*\w+", content))
    metrics.typed_funcs = len(re.findall(r"func\s+\w+\s*\([^)]*:\s*\w+[^)]*\)", content))
    metrics.return_types = len(re.findall(r"\)\s*->\s*\w+\s*:", content))

    # Calculate Godot-specific complexity score
    godot_complexity = (
        metrics.signal_count * 2
        + metrics.export_count
        + metrics.onready_count
        + metrics.node_ref_count * 0.5
        + metrics.connect_count * 2
        + metrics.emit_count
    )

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Adjusted for GDScript
        godot_factor = 1 - (godot_complexity * 0.001)
        type_factor = 1 + (metrics.typed_vars + metrics.typed_funcs) * 0.001

        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 10 * godot_factor
            + 5 * type_factor
        )
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

GenericAnalyzer

Python
GenericAnalyzer()

Bases: LanguageAnalyzer

Generic analyzer for unsupported file types.

Provides basic analysis for text-based files including: - Line and character counting - Basic pattern matching for imports/includes - Simple complexity estimation - Keyword extraction - Configuration file parsing (JSON, YAML, XML, etc.)

This analyzer serves as a fallback for files without specific language support and can handle various text formats.

Initialize the generic analyzer with logger.

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def __init__(self):
    """Initialize the generic analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract potential imports/includes from generic text.

Looks for common import patterns across various languages and configuration files.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with detected imports

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract potential imports/includes from generic text.

    Looks for common import patterns across various languages
    and configuration files.

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with detected imports
    """
    imports = []
    lines = content.split("\n")

    # Common import/include patterns
    patterns = [
        # Include patterns (C-style, various scripting languages)
        (r"^\s*#include\s+<([^>]+)>", "include"),  # angle includes
        (r'^\s*#include\s+"([^"]+)"', "include"),  # quote includes
        (r"^\s*include\s+[\'\"]([^\'\"]+)[\'\"]", "include"),
        # CMake include()
        (r"^\s*include\s*\(\s*([^)\s]+)\s*\)", "include"),
        # Import patterns (various languages)
        (r'^\s*import\s+[\'"]([^\'"]+)[\'"]', "import"),  # import "module"
        (r"^\s*import\s+([A-Za-z_][\w\.]*)\b", "import"),  # import os
        (r'^\s*from\s+[\'"]([^\'"]+)[\'"]', "from"),  # from "mod"
        (r"^\s*from\s+([A-Za-z_][\w\.]*)\s+import\b", "from"),  # from pkg import X
        (r'^\s*require\s+[\'"]([^\'"]+)[\'"]', "require"),
        # PHP/Perl and JS style use statements
        (r"^\s*use\s+([\\\w:]+);?", "use"),  # use Data::Dumper; or use Foo\Bar;
        # Load/source patterns (shell scripts)
        (r'^\s*source\s+[\'"]?([^\'"]+)[\'"]?', "source"),
        (r'^\s*\.[ \t]+[\'"]?([^\'"]+)[\'"]?', "source"),
        # Configuration file references
        (r'[\'"]?(?:file|path|src|href|url)[\'"]?\s*[:=]\s*[\'"]([^\'"]+)[\'"]', "reference"),
    ]

    captured_modules: set[str] = set()

    for i, line in enumerate(lines, 1):
        # Skip comments (generic comment patterns) but keep C preprocessor includes
        if (
            line.strip().startswith("#") and not re.match(r"^\s*#include\b", line)
        ) or line.strip().startswith("//"):
            continue

        for pattern, import_type in patterns:
            match = re.search(pattern, line, re.IGNORECASE)
            if match:
                module = match.group(1)
                imports.append(
                    ImportInfo(
                        module=module,
                        line=i,
                        type=import_type,
                        is_relative=self._is_relative_path(module),
                    )
                )
                captured_modules.add(module)
                break

        # Special case: 'use strict;' (JavaScript directive)
        if re.match(r"^\s*use\s+strict\s*;?\s*$", line):
            imports.append(ImportInfo(module="strict", line=i, type="use", is_relative=False))
            captured_modules.add("strict")

    # Special handling for specific file types
    if file_path.suffix.lower() in [".json", ".yaml", ".yml"]:
        imports.extend(self._extract_config_dependencies(content, file_path))

    # Detect standalone file references like config.yml in logs
    file_ref_pattern = re.compile(
        r"\b([\w./-]+\.(?:ya?ml|json|conf|cfg|ini|xml|toml|log|txt|sh))\b"
    )
    for i, line in enumerate(lines, 1):
        for m in file_ref_pattern.finditer(line):
            module = m.group(1)
            if module not in captured_modules:
                imports.append(
                    ImportInfo(
                        module=module,
                        line=i,
                        type="reference",
                        is_relative=self._is_relative_path(module),
                    )
                )
                captured_modules.add(module)

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract potential exports from generic text.

Looks for common export patterns and definitions.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of potential exported symbols

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract potential exports from generic text.

    Looks for common export patterns and definitions.

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        List of potential exported symbols
    """
    exports = []

    # Common export/definition patterns
    patterns = [
        # Function-like definitions
        (r"^(?:function|def|func|sub|proc)\s+(\w+)", "function"),
        (r"^(\w+)\s*\(\)\s*\{", "function"),
        # Class-like definitions
        (r"^(?:class|struct|type|interface)\s+(\w+)", "class"),
        # Variable/constant definitions
        (r"^(?:export\s+)?(?:const|let|var|val)\s+(\w+)\s*=", "variable"),
        (r'^(\w+)\s*=\s*[\'"]?[^\'"\n]+[\'"]?', "assignment"),
        # Export statements
        (r"^export\s+(\w+)", "export"),
        (r"^module\.exports\.(\w+)", "export"),
    ]

    for pattern, export_type in patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            name = match.group(1)
            exports.append(
                {
                    "name": name,
                    "type": export_type,
                    "line": content[: match.start()].count("\n") + 1,
                }
            )

    # For configuration files, extract top-level keys
    if file_path.suffix.lower() in [".json", ".yaml", ".yml", ".toml", ".ini"]:
        exports.extend(self._extract_config_keys(content, file_path))

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract basic structure from generic text.

Attempts to identify structural elements using pattern matching and indentation analysis.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with detected elements

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract basic structure from generic text.

    Attempts to identify structural elements using pattern matching
    and indentation analysis.

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with detected elements
    """
    structure = CodeStructure()

    # Detect file type category
    file_type = self._detect_file_type(file_path)
    structure.file_type = file_type

    # Detect common YAML-based frameworks/configs
    try:
        if file_path.suffix.lower() in [".yaml", ".yml"]:
            # Initialize modules collection if not present
            if not hasattr(structure, "modules"):
                structure.modules = []

            if self._is_docker_compose_file(file_path, content):
                structure.framework = "docker-compose"
                for svc in self._extract_compose_services(content):
                    structure.modules.append({"type": "service", **svc})
            elif self._looks_like_kubernetes_yaml(content):
                structure.framework = "kubernetes"
                for res in self._extract_k8s_resources(content):
                    structure.modules.append({"type": "resource", **res})
            else:
                # Helm/Kustomize/GitHub Actions quick hints
                name = file_path.name.lower()
                if name == "chart.yaml":
                    structure.framework = "helm"
                elif name == "values.yaml":
                    structure.framework = getattr(structure, "framework", None) or "helm"
                elif name == "kustomization.yaml":
                    structure.framework = "kustomize"
                elif ".github" in str(file_path).replace("\\", "/") and "/workflows/" in str(
                    file_path
                ).replace("\\", "/"):
                    structure.framework = "github-actions"
    except Exception:
        # Never fail generic structure on heuristics
        pass

    # Extract functions (various patterns)
    function_patterns = [
        r"^(?:async\s+)?(?:function|def|func|sub|proc)\s+(\w+)",
        r"^(\w+)\s*\(\)\s*\{",
        r"^(\w+)\s*:\s*function",
        r"^(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>",
    ]

    for pattern in function_patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            func_name = match.group(1)
            structure.functions.append(
                FunctionInfo(name=func_name, line=content[: match.start()].count("\n") + 1)
            )

    # Extract classes/types
    class_patterns = [
        r"^(?:export\s+)?(?:class|struct|type|interface|enum)\s+(\w+)",
        r"^(\w+)\s*=\s*class\s*\{",
    ]

    for pattern in class_patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            class_name = match.group(1)
            structure.classes.append(
                ClassInfo(name=class_name, line=content[: match.start()].count("\n") + 1)
            )

    # Extract sections (markdown headers, etc.)
    if file_type in ["markdown", "documentation", "markup"]:
        section_pattern = r"^(#{1,6})\s+(.+)$"
        for match in re.finditer(section_pattern, content, re.MULTILINE):
            level = len(match.group(1))
            title = match.group(2)
            structure.sections.append(
                {
                    "title": title,
                    "level": level,
                    "line": content[: match.start()].count("\n") + 1,
                }
            )

    # Extract variables/constants
    var_patterns = [
        r"^(?:const|let|var|val)\s+(\w+)",
        r"^(\w+)\s*[:=]\s*[^=]",
        r"^export\s+(\w+)",
    ]

    for pattern in var_patterns:
        for match in re.finditer(pattern, content, re.MULTILINE):
            var_name = match.group(1)
            structure.variables.append(
                {
                    "name": var_name,
                    "line": content[: match.start()].count("\n") + 1,
                    "type": "variable",
                }
            )

    # Detect constants (UPPERCASE variables)
    const_pattern = r"^([A-Z][A-Z0-9_]*)\s*[:=]"
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        structure.constants.append(match.group(1))

    # Extract TODO/FIXME comments
    todo_pattern = r"(?:#|//|/\*|\*)\s*(TODO|FIXME|HACK|NOTE|XXX|BUG):\s*(.+)"
    for match in re.finditer(todo_pattern, content, re.IGNORECASE):
        structure.todos.append(
            {
                "type": match.group(1).upper(),
                "message": match.group(2).strip(),
                "line": content[: match.start()].count("\n") + 1,
            }
        )

    # Count blocks (based on indentation or braces)
    structure.block_count = content.count("{")
    structure.indent_levels = self._analyze_indentation(content)

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate basic complexity metrics for generic text.

Provides simplified complexity estimation based on: - Line count and length - Nesting depth (indentation/braces) - Decision keywords - File type specific metrics

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with basic metrics

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate basic complexity metrics for generic text.

    Provides simplified complexity estimation based on:
    - Line count and length
    - Nesting depth (indentation/braces)
    - Decision keywords
    - File type specific metrics

    Args:
        content: File content
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with basic metrics
    """
    metrics = ComplexityMetrics()

    # Basic line metrics
    lines = content.split("\n")
    # Trim leading/trailing empty lines for line count to match human expectations/tests
    start = 0
    end = len(lines)
    while start < end and lines[start].strip() == "":
        start += 1
    while end > start and lines[end - 1].strip() == "":
        end -= 1
    trimmed_lines = lines[start:end]

    # Preserve historical/test expectation: an entirely empty file counts as 1 line (logical line),
    # while code_lines will be 0. Non-empty (after trimming) counts actual trimmed lines.
    if not trimmed_lines:
        metrics.line_count = 1
    else:
        metrics.line_count = len(trimmed_lines)
    # Character count: count characters, and if file doesn't end with newline, count implicit final EOL
    metrics.character_count = len(content) + (0 if content.endswith("\n") else 1)

    # Count comment lines (generic patterns)
    comment_patterns = [
        r"^\s*#",  # Hash comments
        r"^\s*//",  # Double slash comments
        r"^\s*/\*",  # Block comment start
        r"^\s*\*",  # Block comment continuation
        r"^\s*<!--",  # HTML/XML comments
        r"^\s*;",  # Semicolon comments (INI, assembly)
        r"^\s*--",  # SQL/Lua comments
        r"^\s*%",  # LaTeX/MATLAB comments
    ]

    comment_lines = 0
    for line in trimmed_lines:
        if any(re.match(pattern, line) for pattern in comment_patterns):
            comment_lines += 1

    # Compute code lines as total lines minus comment lines (consistent with tests)
    # For empty file (line_count==1 but no trimmed lines), code_lines should be 0
    if not trimmed_lines:
        metrics.code_lines = 0
    else:
        metrics.code_lines = metrics.line_count - comment_lines

    metrics.comment_lines = comment_lines
    metrics.comment_ratio = comment_lines / metrics.line_count if metrics.line_count > 0 else 0

    # Estimate cyclomatic complexity (decision points)
    decision_keywords = [
        r"\bif\b",
        r"\belse\b",
        r"\belif\b",
        r"\belsif\b",
        r"\bfor\b",
        r"\bwhile\b",
        r"\bdo\b",
        r"\bcase\b",
        r"\bwhen\b",
        r"\btry\b",
        r"\bcatch\b",
        r"\bexcept\b",
        r"\bunless\b",
        r"\buntil\b",
        r"\bswitch\b",
        r"\b\?\s*[^:]+\s*:",
        r"\|\|",
        r"&&",
        r"\band\b",
        r"\bor\b",
    ]

    complexity = 1  # Base complexity
    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content, re.IGNORECASE))

    metrics.cyclomatic = min(complexity, 50)  # Cap at 50 for generic files

    # Estimate nesting depth
    max_depth = 0
    current_depth = 0

    for line in lines:
        # Track braces
        current_depth += line.count("{") - line.count("}")
        current_depth += line.count("(") - line.count(")")
        current_depth += line.count("[") - line.count("]")
        max_depth = max(max_depth, current_depth)

        # Reset if negative (mismatched brackets)
        current_depth = max(current_depth, 0)

    # Also check indentation depth
    indent_depth = self._calculate_max_indent(lines)
    # Combine and cap at 10
    metrics.max_depth = min(max(max_depth, indent_depth), 10)

    # File type specific metrics
    file_type = self._detect_file_type(file_path)

    if file_type == "configuration":
        # For config files, count keys/sections
        metrics.key_count = len(re.findall(r"^\s*[\w\-\.]+\s*[:=]", content, re.MULTILINE))
        metrics.section_count = len(re.findall(r"^\s*\[[\w\-\.]+\]", content, re.MULTILINE))

    elif file_type == "markup":
        # For markup files, count tags
        metrics.tag_count = len(re.findall(r"<\w+", content))
        metrics.header_count = len(re.findall(r"^#{1,6}\s+", content, re.MULTILINE))

    elif file_type == "data":
        # For data files, estimate structure
        if file_path.suffix.lower() == ".csv":
            lines_sample = lines[:10] if len(lines) > 10 else lines
            if lines_sample:
                # Estimate columns
                metrics.column_count = len(lines_sample[0].split(","))
                metrics.row_count = len(lines) - 1  # Exclude header

    # Calculate a simple maintainability index
    if metrics.code_lines > 0:
        # Simplified calculation
        maintainability = 100

        # Penalize high complexity
        maintainability -= min(30, complexity * 0.5)

        # Penalize deep nesting
        maintainability -= min(20, metrics.max_depth * 2)

        # Reward comments
        maintainability += min(10, metrics.comment_ratio * 30)

        # Penalize very long files
        if metrics.line_count > 1000:
            maintainability -= 10
        elif metrics.line_count > 500:
            maintainability -= 5

        metrics.maintainability_index = max(0, min(100, maintainability))

    return metrics

extract_context_relevant_sections

Python
extract_context_relevant_sections(content: str, file_path: Path, prompt_keywords: List[str], search_depth: int = 2, min_confidence: float = 0.6, max_sections: int = 10) -> Dict[str, Any]

Extract sections of documentation that reference prompt keywords/concepts.

This method identifies and extracts the most relevant parts of documentation files based on direct references and semantic similarity to prompt keywords.

PARAMETERDESCRIPTION
content

File content

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

prompt_keywords

Keywords/phrases from the user's prompt

TYPE:List[str]

search_depth

How deep to search (1=direct, 2=semantic, 3=deep analysis)

TYPE:intDEFAULT:2

min_confidence

Minimum confidence threshold for relevance (0.0-1.0)

TYPE:floatDEFAULT:0.6

max_sections

Maximum number of contextual sections to preserve

TYPE:intDEFAULT:10

RETURNSDESCRIPTION
Dict[str, Any]

Dictionary containing relevant sections with metadata

Source code in tenets/core/analysis/implementations/generic_analyzer.py
Python
def extract_context_relevant_sections(
    self,
    content: str,
    file_path: Path,
    prompt_keywords: List[str],
    search_depth: int = 2,
    min_confidence: float = 0.6,
    max_sections: int = 10,
) -> Dict[str, Any]:
    """Extract sections of documentation that reference prompt keywords/concepts.

    This method identifies and extracts the most relevant parts of documentation
    files based on direct references and semantic similarity to prompt keywords.

    Args:
        content: File content
        file_path: Path to the file being analyzed
        prompt_keywords: Keywords/phrases from the user's prompt
        search_depth: How deep to search (1=direct, 2=semantic, 3=deep analysis)
        min_confidence: Minimum confidence threshold for relevance (0.0-1.0)
        max_sections: Maximum number of contextual sections to preserve

    Returns:
        Dictionary containing relevant sections with metadata
    """
    if not prompt_keywords:
        return {
            "relevant_sections": [],
            "metadata": {"total_sections": 0, "matched_sections": 0},
        }

    file_type = self._detect_file_type(file_path)

    # Extract sections based on file type
    sections = self._extract_document_sections(content, file_path, file_type)

    # Score sections based on relevance to prompt keywords
    scored_sections = []
    for section in sections:
        score, matches = self._calculate_section_relevance(
            section, prompt_keywords, search_depth
        )

        if score >= min_confidence:
            scored_sections.append(
                {
                    **section,
                    "relevance_score": score,
                    "keyword_matches": matches,
                    "context_type": self._determine_context_type(section, matches),
                }
            )

    # Sort by relevance and limit to max_sections
    scored_sections.sort(key=lambda x: x["relevance_score"], reverse=True)
    relevant_sections = scored_sections[:max_sections]

    # Extract code examples and references within relevant sections
    for section in relevant_sections:
        section["code_examples"] = self._extract_code_examples_from_section(section)
        section["api_references"] = self._extract_api_references_from_section(section)
        section["config_references"] = self._extract_config_references_from_section(section)

    metadata = {
        "total_sections": len(sections),
        "matched_sections": len(scored_sections),
        "relevant_sections": len(relevant_sections),
        "file_type": file_type,
        "search_depth": search_depth,
        "min_confidence": min_confidence,
        "avg_relevance_score": (
            sum(s["relevance_score"] for s in relevant_sections) / len(relevant_sections)
            if relevant_sections
            else 0.0
        ),
    }

    return {"relevant_sections": relevant_sections, "metadata": metadata}

GoAnalyzer

Python
GoAnalyzer()

Bases: LanguageAnalyzer

Go code analyzer.

Provides comprehensive analysis for Go files including: - Import analysis with vendored and internal imports - Function, method and interface extraction - Struct analysis with embedded types - Goroutine and channel detection - Error handling patterns - Defer statement tracking - Package-level analysis - Go module support

Go's export mechanism is based on capitalization - identifiers starting with uppercase letters are exported.

Initialize the Go analyzer with logger.

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def __init__(self):
    """Initialize the Go analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract imports from Go code.

Handles: - Single imports: import "fmt" - Grouped imports: import ( "fmt" "strings" ) - Aliased imports: import f "fmt" - Dot imports: import . "fmt" - Blank imports: import _ "database/sql" - Vendored imports - Internal packages

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[ImportInfo]

List of ImportInfo objects with import details

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def extract_imports(self, content: str, file_path: Path) -> List[ImportInfo]:
    """Extract imports from Go code.

    Handles:
    - Single imports: import "fmt"
    - Grouped imports: import ( "fmt" "strings" )
    - Aliased imports: import f "fmt"
    - Dot imports: import . "fmt"
    - Blank imports: import _ "database/sql"
    - Vendored imports
    - Internal packages

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        List of ImportInfo objects with import details
    """
    imports = []
    lines = content.split("\n")

    import_block = False
    import_block_start = 0

    for i, line in enumerate(lines, 1):
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Single import statement
        single_import = re.match(r'^\s*import\s+"([^"]+)"', line)
        if single_import:
            imports.append(
                ImportInfo(
                    module=single_import.group(1),
                    line=i,
                    type="import",
                    is_relative=False,
                    is_vendored=self._is_vendored_import(single_import.group(1)),
                    is_internal="internal" in single_import.group(1),
                )
            )
            continue

        # Aliased single import
        aliased_import = re.match(r'^\s*import\s+(\w+)\s+"([^"]+)"', line)
        if aliased_import:
            imports.append(
                ImportInfo(
                    module=aliased_import.group(2),
                    alias=aliased_import.group(1),
                    line=i,
                    type="aliased",
                    is_relative=False,
                    is_vendored=self._is_vendored_import(aliased_import.group(2)),
                )
            )
            continue

        # Dot import
        dot_import = re.match(r'^\s*import\s+\.\s+"([^"]+)"', line)
        if dot_import:
            imports.append(
                ImportInfo(
                    module=dot_import.group(1),
                    alias=".",
                    line=i,
                    type="dot_import",
                    is_relative=False,
                )
            )
            continue

        # Blank import
        blank_import = re.match(r'^\s*import\s+_\s+"([^"]+)"', line)
        if blank_import:
            imports.append(
                ImportInfo(
                    module=blank_import.group(1),
                    alias="_",
                    line=i,
                    type="blank_import",
                    is_relative=False,
                    purpose="side_effects",
                )
            )
            continue

        # Import block start
        if re.match(r"^\s*import\s*\(", line):
            import_block = True
            import_block_start = i
            continue

        # Inside import block
        if import_block:
            # Check for end of import block
            if ")" in line:
                import_block = False
                continue

            # Standard import in block
            standard_import = re.match(r'^\s*"([^"]+)"', line)
            if standard_import:
                module = standard_import.group(1)
                imports.append(
                    ImportInfo(
                        module=module,
                        line=i,
                        type="import",
                        is_relative=False,
                        is_vendored=self._is_vendored_import(module),
                        is_internal="internal" in module,
                        is_stdlib=self._is_stdlib_import(module),
                    )
                )
                continue

            # Aliased import in block
            aliased_import = re.match(r'^\s*(\w+)\s+"([^"]+)"', line)
            if aliased_import:
                module = aliased_import.group(2)
                imports.append(
                    ImportInfo(
                        module=module,
                        alias=aliased_import.group(1),
                        line=i,
                        type="aliased",
                        is_relative=False,
                        is_vendored=self._is_vendored_import(module),
                    )
                )
                continue

            # Dot import in block
            dot_import = re.match(r'^\s*\.\s+"([^"]+)"', line)
            if dot_import:
                imports.append(
                    ImportInfo(
                        module=dot_import.group(1),
                        alias=".",
                        line=i,
                        type="dot_import",
                        is_relative=False,
                    )
                )
                continue

            # Blank import in block
            blank_import = re.match(r'^\s*_\s+"([^"]+)"', line)
            if blank_import:
                imports.append(
                    ImportInfo(
                        module=blank_import.group(1),
                        alias="_",
                        line=i,
                        type="blank_import",
                        is_relative=False,
                        purpose="side_effects",
                    )
                )

    # Categorize imports
    self._categorize_imports(imports)

    return imports

extract_exports

Python
extract_exports(content: str, file_path: Path) -> List[Dict[str, Any]]

Extract exported symbols from Go code.

In Go, exported identifiers start with an uppercase letter. This includes functions, types, constants, and variables.

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
List[Dict[str, Any]]

List of exported symbols with metadata

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def extract_exports(self, content: str, file_path: Path) -> List[Dict[str, Any]]:
    """Extract exported symbols from Go code.

    In Go, exported identifiers start with an uppercase letter.
    This includes functions, types, constants, and variables.

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        List of exported symbols with metadata
    """
    exports = []

    # Extract package name
    package_match = re.search(r"^\s*package\s+(\w+)", content, re.MULTILINE)
    package_name = package_match.group(1) if package_match else "unknown"

    # Exported functions
    func_pattern = (
        r"^func\s+([A-Z][a-zA-Z0-9]*)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?\s*\{"
    )
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        params = match.group(2)
        return_params = match.group(3)
        return_type = match.group(4)

        exports.append(
            {
                "name": func_name,
                "type": "function",
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
                "signature": self._build_function_signature(
                    func_name, params, return_params, return_type
                ),
                "has_receiver": False,
            }
        )

    # Exported methods (with receivers)
    method_pattern = r"^func\s+\(([^)]+)\)\s+([A-Z][a-zA-Z0-9]*)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?\s*\{"
    for match in re.finditer(method_pattern, content, re.MULTILINE):
        receiver = match.group(1)
        method_name = match.group(2)
        params = match.group(3)
        return_params = match.group(4)
        return_type = match.group(5)

        # Parse receiver type
        receiver_type = self._parse_receiver(receiver)

        exports.append(
            {
                "name": method_name,
                "type": "method",
                "line": content[: match.start()].count("\n") + 1,
                "receiver": receiver_type,
                "package": package_name,
                "signature": self._build_method_signature(
                    receiver, method_name, params, return_params, return_type
                ),
                "has_receiver": True,
            }
        )

    # Exported types (structs, interfaces, type aliases)
    type_pattern = r"^type\s+([A-Z][a-zA-Z0-9]*)\s+(.+?)(?:\n|\{)"
    for match in re.finditer(type_pattern, content, re.MULTILINE):
        type_name = match.group(1)
        type_def = match.group(2).strip()

        # Determine type kind
        if "struct" in type_def:
            type_kind = "struct"
        elif "interface" in type_def:
            type_kind = "interface"
        elif "=" in type_def:
            type_kind = "alias"
        else:
            type_kind = "type"

        exports.append(
            {
                "name": type_name,
                "type": type_kind,
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
                "definition": type_def[:50] if len(type_def) > 50 else type_def,
            }
        )

    # Exported constants
    const_pattern = r"^const\s+([A-Z][a-zA-Z0-9]*)\s*(?:[\w\s]+)?\s*="
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "constant",
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
            }
        )

    # Exported constant blocks
    const_block_pattern = r"^const\s*\((.*?)\)"
    for match in re.finditer(const_block_pattern, content, re.MULTILINE | re.DOTALL):
        block_content = match.group(1)
        for const_match in re.finditer(r"^\s*([A-Z][a-zA-Z0-9]*)", block_content, re.MULTILINE):
            exports.append(
                {
                    "name": const_match.group(1),
                    "type": "constant",
                    "line": content[: match.start()].count("\n")
                    + block_content[: const_match.start()].count("\n")
                    + 1,
                    "package": package_name,
                    "in_block": True,
                }
            )

    # Exported variables
    var_pattern = r"^var\s+([A-Z][a-zA-Z0-9]*)\s+"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        exports.append(
            {
                "name": match.group(1),
                "type": "variable",
                "line": content[: match.start()].count("\n") + 1,
                "package": package_name,
            }
        )

    return exports

extract_structure

Python
extract_structure(content: str, file_path: Path) -> CodeStructure

Extract code structure from Go file.

Extracts: - Package declaration - Functions and methods - Structs (treated as classes) - Interfaces - Type aliases - Constants and variables - Goroutines and channels - Init functions

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
CodeStructure

CodeStructure object with extracted elements

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def extract_structure(self, content: str, file_path: Path) -> CodeStructure:
    """Extract code structure from Go file.

    Extracts:
    - Package declaration
    - Functions and methods
    - Structs (treated as classes)
    - Interfaces
    - Type aliases
    - Constants and variables
    - Goroutines and channels
    - Init functions

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        CodeStructure object with extracted elements
    """
    structure = CodeStructure()

    # Extract package name
    package_match = re.search(r"^\s*package\s+(\w+)", content, re.MULTILINE)
    if package_match:
        structure.package = package_match.group(1)
        structure.is_main = structure.package == "main"

    # Extract functions
    func_pattern = (
        r"^func\s+(?:\([^)]+\)\s+)?(\w+)\s*\(([^)]*)\)(?:\s*\(([^)]*)\))?(?:\s*([^{]+))?"
    )
    for match in re.finditer(func_pattern, content, re.MULTILINE):
        func_name = match.group(1)
        params = match.group(2)

        # Check for special functions
        is_init = func_name == "init"
        is_main = func_name == "main" and structure.is_main
        is_test = func_name.startswith("Test") or func_name.startswith("Benchmark")

        func_info = FunctionInfo(
            name=func_name,
            line=content[: match.start()].count("\n") + 1,
            args=self._parse_go_params(params),
            is_exported=func_name[0].isupper(),
            is_init=is_init,
            is_main=is_main,
            is_test=is_test,
        )

        structure.functions.append(func_info)

    # Extract structs (as classes)
    struct_pattern = r"^type\s+(\w+)\s+struct\s*\{"
    for match in re.finditer(struct_pattern, content, re.MULTILINE):
        struct_name = match.group(1)

        # Find struct fields
        struct_start = match.end()
        brace_count = 1
        struct_end = struct_start

        for i, char in enumerate(content[struct_start:], struct_start):
            if char == "{":
                brace_count += 1
            elif char == "}":
                brace_count -= 1
                if brace_count == 0:
                    struct_end = i
                    break

        struct_content = content[struct_start:struct_end]
        fields = self._extract_struct_fields(struct_content)

        # Find methods for this struct
        methods = self._find_struct_methods(content, struct_name)

        class_info = ClassInfo(
            name=struct_name,
            line=content[: match.start()].count("\n") + 1,
            is_exported=struct_name[0].isupper(),
            fields=fields,
            methods=methods,
            embedded_types=self._find_embedded_types(struct_content),
        )

        structure.classes.append(class_info)

    # Extract interfaces
    interface_pattern = r"^type\s+(\w+)\s+interface\s*\{"
    for match in re.finditer(interface_pattern, content, re.MULTILINE):
        interface_name = match.group(1)

        # Find interface methods
        interface_start = match.end()
        brace_count = 1
        interface_end = interface_start

        for i, char in enumerate(content[interface_start:], interface_start):
            if char == "{":
                brace_count += 1
            elif char == "}":
                brace_count -= 1
                if brace_count == 0:
                    interface_end = i
                    break

        interface_content = content[interface_start:interface_end]
        methods = self._extract_interface_methods(interface_content)

        structure.interfaces.append(
            {
                "name": interface_name,
                "line": content[: match.start()].count("\n") + 1,
                "is_exported": interface_name[0].isupper(),
                "methods": methods,
                "is_empty": len(methods) == 0,  # Empty interface (interface{})
            }
        )

    # Extract type aliases
    type_alias_pattern = r"^type\s+(\w+)\s*=\s*(.+)$"
    for match in re.finditer(type_alias_pattern, content, re.MULTILINE):
        structure.type_aliases.append(
            {
                "name": match.group(1),
                "base_type": match.group(2).strip(),
                "line": content[: match.start()].count("\n") + 1,
                "is_exported": match.group(1)[0].isupper(),
            }
        )

    # Extract custom type definitions
    type_def_pattern = r"^type\s+(\w+)\s+(\w+)$"
    for match in re.finditer(type_def_pattern, content, re.MULTILINE):
        if not re.match(r"^type\s+\w+\s+(?:struct|interface)", content[match.start() :]):
            structure.type_definitions.append(
                {
                    "name": match.group(1),
                    "base_type": match.group(2),
                    "line": content[: match.start()].count("\n") + 1,
                    "is_exported": match.group(1)[0].isupper(),
                }
            )

    # Extract constants
    const_pattern = r"^const\s+(\w+)"
    for match in re.finditer(const_pattern, content, re.MULTILINE):
        const_name = match.group(1)
        structure.constants.append(const_name)
        structure.variables.append(
            {
                "name": const_name,
                "line": content[: match.start()].count("\n") + 1,
                "type": "constant",
                "is_exported": const_name[0].isupper(),
            }
        )

    # Extract variables
    var_pattern = r"^var\s+(\w+)"
    for match in re.finditer(var_pattern, content, re.MULTILINE):
        var_name = match.group(1)
        structure.variables.append(
            {
                "name": var_name,
                "line": content[: match.start()].count("\n") + 1,
                "type": "variable",
                "is_exported": var_name[0].isupper(),
            }
        )

    # Count goroutines
    goroutine_pattern = r"\bgo\s+(?:\w+\.)*\w+\s*\("
    structure.goroutines_count = len(re.findall(goroutine_pattern, content))

    # Count channels
    channel_pattern = r"(?:chan\s+\w+|<-chan\s+\w+|chan<-\s+\w+)"
    structure.channels_count = len(re.findall(channel_pattern, content))

    # Count defer statements
    defer_pattern = r"\bdefer\s+"
    structure.defer_count = len(re.findall(defer_pattern, content))

    # Detect test file
    structure.is_test_file = file_path.name.endswith("_test.go")

    return structure

calculate_complexity

Python
calculate_complexity(content: str, file_path: Path) -> ComplexityMetrics

Calculate complexity metrics for Go code.

Calculates: - Cyclomatic complexity - Cognitive complexity - Error handling complexity - Concurrency complexity - Test coverage indicators

PARAMETERDESCRIPTION
content

Go source code

TYPE:str

file_path

Path to the file being analyzed

TYPE:Path

RETURNSDESCRIPTION
ComplexityMetrics

ComplexityMetrics object with calculated metrics

Source code in tenets/core/analysis/implementations/go_analyzer.py
Python
def calculate_complexity(self, content: str, file_path: Path) -> ComplexityMetrics:
    """Calculate complexity metrics for Go code.

    Calculates:
    - Cyclomatic complexity
    - Cognitive complexity
    - Error handling complexity
    - Concurrency complexity
    - Test coverage indicators

    Args:
        content: Go source code
        file_path: Path to the file being analyzed

    Returns:
        ComplexityMetrics object with calculated metrics
    """
    metrics = ComplexityMetrics()

    # Calculate cyclomatic complexity
    complexity = 1

    decision_keywords = [
        r"\bif\b",
        r"\belse\s+if\b",
        r"\belse\b",
        r"\bfor\b",
        r"\bswitch\b",
        r"\bcase\b",
        r"\bselect\b",
        r"\bdefault\b",
        r"\b&&\b",
        r"\|\|",
    ]

    for keyword in decision_keywords:
        complexity += len(re.findall(keyword, content))

    # Add complexity for range loops
    complexity += len(re.findall(r"\bfor\s+\w+\s*:=\s*range\b", content))

    metrics.cyclomatic = complexity

    # Calculate cognitive complexity
    cognitive = 0
    nesting_level = 0
    max_nesting = 0

    lines = content.split("\n")
    for line in lines:
        # Skip comments
        if line.strip().startswith("//"):
            continue

        # Track nesting
        opening_braces = line.count("{")
        closing_braces = line.count("}")
        nesting_level += opening_braces - closing_braces
        max_nesting = max(max_nesting, nesting_level)

        # Control structures with nesting penalty
        control_patterns = [
            (r"\bif\b", 1),
            (r"\bfor\b", 1),
            (r"\bswitch\b", 1),
            (r"\bselect\b", 2),  # Higher weight for select
            (r"\bcase\b", 0.5),
            (r"\belse\s+if\b", 1),
            (r"\belse\b", 0),
        ]

        for pattern, weight in control_patterns:
            if re.search(pattern, line):
                cognitive += weight * (1 + max(0, nesting_level - 1))

        # Error handling complexity
        if "err != nil" in line:
            cognitive += 1
            metrics.error_handling_count = getattr(metrics, "error_handling_count", 0) + 1

        # Panic/recover complexity
        if re.search(r"\bpanic\b|\brecover\b", line):
            cognitive += 2

    metrics.cognitive = cognitive
    metrics.max_depth = max_nesting

    # Count code elements
    metrics.line_count = len(lines)
    metrics.code_lines = self._count_code_lines(content)
    metrics.comment_lines = self._count_comment_lines(content)
    metrics.comment_ratio = (
        metrics.comment_lines / metrics.line_count if metrics.line_count > 0 else 0
    )

    # Count functions and methods
    metrics.function_count = len(
        re.findall(r"^func\s+(?:\([^)]+\)\s+)?\w+\s*\(", content, re.MULTILINE)
    )

    # Count structs and interfaces
    metrics.struct_count = len(re.findall(r"^type\s+\w+\s+struct\s*\{", content, re.MULTILINE))
    metrics.interface_count = len(
        re.findall(r"^type\s+\w+\s+interface\s*\{", content, re.MULTILINE)
    )

    # Concurrency metrics
    metrics.goroutines_count = len(re.findall(r"\bgo\s+\w+", content))
    metrics.channels_count = len(re.findall(r"chan\s+\w+", content))
    metrics.select_statements = len(re.findall(r"\bselect\s*\{", content))
    metrics.mutex_usage = len(re.findall(r"sync\.(?:Mutex|RWMutex)", content))

    # Error handling metrics
    metrics.error_checks = len(re.findall(r"if\s+err\s*!=\s*nil", content))
    metrics.error_returns = len(re.findall(r"return\s+.*err", content))

    # Test metrics (if test file)
    if file_path.name.endswith("_test.go"):
        metrics.test_count = len(re.findall(r"^func\s+Test\w+\s*\(", content, re.MULTILINE))
        metrics.benchmark_count = len(
            re.findall(r"^func\s+Benchmark\w+\s*\(", content, re.MULTILINE)
        )
        metrics.example_count = len(
            re.findall(r"^func\s+Example\w*\s*\(", content, re.MULTILINE)
        )

    # Calculate maintainability index
    import math

    if metrics.code_lines > 0:
        # Adjusted for Go's error handling patterns
        error_factor = max(0, 1 - (metrics.error_checks / metrics.code_lines))
        mi = (
            171
            - 5.2 * math.log(max(1, complexity))
            - 0.23 * complexity
            - 16.2 * math.log(metrics.code_lines)
            + 20 * error_factor
        )  # Bonus for proper error handling
        metrics.maintainability_index = max(0, min(100, mi))

    return metrics

HTMLAnalyzer

Python
HTMLAnalyzer()

Bases: LanguageAnalyzer

HTML code analyzer with modern web framework support.

Provides comprehensive analysis for HTML files including: - HTML5 semantic elements - CSS and JavaScript imports - Meta tags and SEO elements - Forms and input validation - Accessibility features (ARIA, alt text, etc.) - Web components and custom elements - Framework-specific patterns (React, Vue, Angular) - Microdata and structured data - DOM complexity and nesting depth - Performance hints (lazy loading, async/defer scripts) - Security considerations (CSP, integrity checks)

Supports HTML5 and modern web development practices.

Initialize the HTML analyzer with logger.

Source code in tenets/core/analysis/implementations/html_analyzer.py
Python
def __init__(self):
    """Initialize the HTML analyzer with logger."""
    self.logger = get_logger(__name__)

extract_imports

Python
extract_imports(content: str, file_path: Path) -> List[ImportInfo]

Extract external resource imports from HTML.

Handles: - tags for CSS -